InteroperabilityHealthcare EDIRevenue Cycle

Waystar Integration and API Guide: Clearinghouse Setup From a Live Build

August 11, 202619 min readUpdated Nov 4, 2026
Written by
Jitendra Choudhary
Jitendra Choudhary

CTO & Co-Founder

CTO & Co-Founder at Nirmitee.io. Architects healthcare integrations across FHIR, SMART on FHIR, ABDM and NHCX, writing from production experience taking hospital software from sandbox to go-live.

Waystar Integration and API Guide: Clearinghouse Setup From a Live Build

Updated November 2026

A Waystar integration connects your EHR or billing system to the Waystar clearinghouse so your claims go out as 837 files and the answers come back as 999, 277CA, 277 and 835 files, either as batch files over SFTP or, where your contract includes it, through the Waystar API. Getting it right takes four pieces of work: the contract and payer enrollment, the SFTP or API connection, a correct 837, and code that reads every file that comes back, including the claim status you have to ask for.

This guide is built from our own Waystar clearinghouse integration work. Where we describe how Waystar behaved, that is what we observed on our connection; your contract and Waystar's companion guide are the final word. For the full claim journey around it, see our rcm integration guide. If you want a team to build or fix the connection, see our ehr clearinghouse integration service or talk to our team.

Watch: 999 vs 277CA: The Most Confusing RCM Difference Explained for Engineers (9:46).

Watch: How 277CA Claim Rejections Actually Work: STC Codes and a Safe Rejection Workflow (9:12).

Key takeaways

  • Start payer enrollment and the IP allowlist request before you write code. On our project both took longer than the engineering.
  • Waystar runs a developer portal with API documentation, but the documentation pages need a login. Confirm whether the Waystar API is in your contract; ours covered SFTP batch only.
  • A 999 "accepted" means the file parsed, not that your claims were accepted. Claim rejections arrive in the 277CA.
  • The 277CA and the payer's 277 status response both start with ST*277. Route them by BHT06 (TH or DG) and the implementation guide version, never by "277" alone.
  • Claim status does not arrive on its own. Build a 276 polling loop from day one.
  • Save every submission before you send it and retry with the same control numbers, so a retry never becomes a duplicate claim.

What Waystar does in your claim flow

Waystar sits between your billing system and the payers as a clearinghouse. You send it your claims in one standard format, it checks them and forwards them to each payer, and it sends back acknowledgements, payer responses and remittances.

So, is Waystar a clearing house? Yes. On our connection it received our 837 claim files over SFTP and returned 999 file acknowledgements, 277CA claim acknowledgements and 835 remittances, which is the clearinghouse role. Waystar also sells revenue cycle software beyond the clearinghouse; this guide covers only the claims connection.

Without a clearinghouse, you would need separate enrollment, connectivity, testing and upkeep for every payer. That is why most providers and billing platforms use one for all but their very largest payers.

Waystar and ZirMed: why your files still say ZIRMED

Waystar is the company formed when Navicure and ZirMed merged in November 2017; the combined company took the Waystar name in February 2018, according to Waystar's own announcement. So if you are asking what ZirMed is today, it is part of Waystar.

The ZirMed name still shows up in the integration. On our connection, the X12 files Waystar sent us carried ZIRMED as the sender ID in the ISA envelope (ISA06, with qualifier ZZ), and the XML portal reports it dropped in our download folder referenced a zirmed.com report stylesheet. If you see ZIRMED in a file, it came from Waystar. Confirm the sender and receiver IDs for your own account in writing before your first test file.

Before you write code: contract, API access and enrollment

The slowest parts of a Waystar integration are paperwork and network access, not code. Start the contract questions, payer enrollment and IP allowlisting on day one.

  • The contract was per EHR, not per clinic. On our project, one Waystar account covered many practices, each with its own tax ID. The billing provider's tax ID travels inside the 837, not in the connection, so we did not need separate SFTP credentials for each clinic.
  • The API was a separate product. Our contract covered SFTP batch only. If you plan real-time eligibility or claim status calls, ask whether the API is entitled before you design screens around it.
  • IP allowlisting took days. SFTP access was limited to registered IP addresses, and adding one was not same-day. It is the most common reason we have seen a first sprint stall.
  • Payer enrollment took weeks. Enrollment is per payer, per provider and per transaction type: claims, electronic remittance (ERA) and electronic funds transfer (EFT) are separate. Some payers want test files first. Gate your go-live date on enrollment, not on the code.

Waystar API documentation: what is public and what needs a login

Waystar publishes a developer portal at developer.waystar.com, linked as "Developer Login" from waystar.com. The portal's home page is public; the documentation pages behind it ask you to register and log in. We checked this on November 4, 2026.

The public home page lists the ways Waystar connects: REST APIs, web services, SFTP, HL7, batch, X12 EDI and RPA bots. Its documentation menu is grouped by product, including integration basics (SSH FTP instructions, HMAC security for web services, a payer ID list, test and sandbox accounts), insurance verification, claim management, claim monitoring, remit management and denial management.

So for "waystar api documentation", the answer is: the documentation exists, but you read it as a registered developer, and what you can call in production depends on your contract. On our project the API was a separate product and was not in the contract, so we built on SFTP batch files. If your scope needs real-time eligibility or claim status, ask Waystar two things before you design screens around it: which transactions your contract includes by API, and whether a test account is available.

Connecting over SFTP: folders and file names

On our connection, Waystar exchanged files through three SFTP folders: one for uploads, one for downloads and one archive. You put 837 files in the upload folder and poll the download folder for everything that comes back.

FolderWhat we sawWhat to build
/UploadOutbound claim and eligibility files; write-only for our accountKeep your own record of every file sent; you cannot list it back
/Download999, 277CA, 277, 271, 835 and report filesPoll on a schedule, download, then route by content
/Download/ArchiveFiles already collectedUse it to recover a file your poller missed

In our setup the file extension told Waystar what kind of file we were sending: .CLP for professional 837P claims, .CLI for institutional 837I, .CLD for dental 837D, and .ELG for 270 eligibility batches. The base file name could not contain periods. Treat these as the conventions in your companion guide, which may differ.

Do not route inbound files by their names. Read the envelope (ISA, then GS, then ST) and route by content. File names are a convenience, not part of the X12 standard, and they can change.

On our connection, uploading a file with a name that was already used replaced the earlier file without warning. Since the upload folder cannot be listed, put a timestamp or batch ID in every base file name, for example claims_20261104T1830Z_001.CLP.

Credentials and the host key

One set of SFTP credentials can submit claims for every practice on your platform, so treat it like a production database password. Keep it in a secrets manager or environment variables, never in committed configuration, and refuse to start in production if any value is missing.

# Startup check: refuse to boot in production with missing settings
required = ["SFTP_HOST", "SFTP_USERNAME", "SFTP_PASSWORD", "HOST_KEY_SHA256", "USAGE_INDICATOR"]
missing  = [k for k in required if not config.get(k)]
if missing and env != "development":
    raise ConfigError(f"Refusing to start. Unset: {', '.join(missing)}")
log.info("X12 usage indicator ISA15=%s", config["USAGE_INDICATOR"])   # T or P

Pin the SFTP host key. Record its fingerprint on your first verified connection and refuse to connect in production when it does not match, so a changed key is a visible event and not a silent one. Make the ISA15 usage indicator (T for test, P for production) a setting rather than a constant, and log it at startup, so anyone can see which mode a running system is in.

Sending the 837 so every answer can find its claim

The 837 itself is standard X12, so the work is mostly in identifiers. Every file needs unique control numbers, and every claim needs a unique claim ID that stays the same if you resend it.

  • ISA13 and GS06 are the interchange and group control numbers. The 999 refers back to them, so they must be unique per file.
  • CLM01 is your patient control number for the claim. It comes back in the 277CA and as CLP01 in the 835, so it is your key for matching every answer to its claim. Keep it the same across retries.
  • Provider loops name the billing provider (NM1*85), the rendering provider (NM1*82) and, when used, the referring provider (NM1*DN), with the NPI after the XX qualifier.
  • The billing tax ID sits in the claim, which is why one Waystar account can carry many practices.

Our 835 vs 837 guide covers the segments in detail, and our guide to rendering vs billing provider covers the provider loops.

Submitting claims without duplicates

A duplicate claim is worse than a failed one, so save each submission before you send it and retry with the same control numbers. Transmission failures are routine on SFTP; the design has to make them recoverable.

def submit_claim(claim_id):
    blockers = validate_submission_readiness(claim_id)    # demographics, coverage, NPI, diagnosis
    if blockers:
        return Blocked(blockers[0])
    if submissions.exists(claim_id=claim_id, status_in=["pending", "transmitted"]):
        return Blocked("A live submission already exists")

    # Save first, with control numbers fixed now and never regenerated
    s = submissions.create(claim_id=claim_id,
                           isa13=next_interchange_control_number(),
                           gs06=next_group_control_number(),
                           clm01=f"PCN{claim_id}", status="pending",
                           filename=f"claims_{utc_now():%Y%m%dT%H%M%SZ}_{claim_id}.CLP")
    send(s)

def send(s):
    try:
        transport.put(build_837p(s.claim_id, s.isa13, s.gs06), s.filename)
        s.mark_transmitted()
    except TransportError as e:
        s.attempts += 1
        s.last_error = str(e)[:1024]
        if s.attempts >= MAX_ATTEMPTS:
            s.abandon()                          # visible state, not silence
            claims.revert_to_ready(s.claim_id)
        else:
            s.retry_at = utc_now() + backoff(s.attempts)

def retry_sweep():
    for s in submissions.due_for_retry():
        if acks_999.exists(isa13=s.isa13):       # it did arrive; do not send it again
            s.mark_transmitted()
        else:
            send(s)                              # same ISA13, GS06 and CLM01

Three rules carry most of the weight:

  • Check before you build. Missing demographics, inactive coverage, a missing NPI or a non-billable diagnosis are cheaper to catch now than as a rejection days later. Eligibility is usually the best first fix; see our guide to automating eligibility verification.
  • Save before you send. If the process dies mid-upload, the record exists and can be recovered. Without it you have a claim that may or may not have been filed.
  • Keep delivery state apart from claim state. A claim can be "submitted" in your workflow while its file never reached Waystar. Track both, so an unsent claim never looks filed.

Matching answers to claims follows the same discipline. Never rewrite the payer's identifiers to force a match; record a manual match as a separate row. Never discard a response you cannot match; park it in a triage queue, because an unmatched file usually means a matching bug.

The files that come back: 999, 277CA, 277 and 835

Three kinds of file come back without you asking: the 999, the 277CA and the 835. The fourth, the payer's 277 claim status response, only comes when you send a 276.

FileWhat it tells youMatch it byWhat to do
999The file and each transaction set passed or failed syntax checksISA13 and GS06 of the file you sentIf rejected, fix the syntax and resend the file
277CA (005010X214)Each claim was accepted into processing or rejected, with a reasonCLM01, returned in the 277CASend rejections to a worklist; a rejected claim never reached the payer
277 (005010X212)Where the payer is with a claim: pending, paid, deniedThe trace number you sent in the 276Update status; answer P3 "provider requested information" quickly
835 (005010X221)What was paid, adjusted and denied, and whyCLP01 (your CLM01), then the payer claim number in CLP07Post payments, route denials, match the EFT by TRN

A 999 "accepted" is the most misread file in claims work. In a live clearinghouse integration we hardened, every one of the 1,728 999s in our first review was accepted, while 277CAs were still rejecting claims for content problems, most often diagnosis codes. Our clearinghouse claims integration case study has the details.

Keep your own copy of every file that comes back. Download, store it durably with the claim it matched, then clear it from your working set. Waystar's archive folder is a convenience for recovering a missed file, not your record; if a payer disputes timely filing months later, the 277CA that proves it should be in your own storage.

Portal reports: when someone edits a claim inside Waystar

Not everything in the download folder is X12. On our connection Waystar also dropped XML portal reports (named like Prof_<customer id>_<date>.xml), and they recorded billers correcting claims by hand inside the Waystar portal, most often diagnosis codes. When that happens, your system's copy of the claim is out of date and the same mistake comes back on the next claim.

So do not let these files pile up as "unrecognized". Detect them in the router (they do not start with ISA), and raise a work item that says "this claim was edited at the clearinghouse, update it here". We do not have a published schema for these reports, so parse them defensively and keep the raw file.

277 vs 277CA: the routing trap

The 277CA and the payer's 277 claim status response share the same transaction code, ST01 = 277. They differ in BHT06 (TH for the 277CA, DG for a status response) and in the implementation guide version (005010X214 vs 005010X212), so route on those, not on "277".

The 277CA is an acknowledgement: it says whether each claim got into processing. The 277 status response is the payer's answer to your 276: where the claim is in adjudication. If both go to one parser, a payer status will be read as an acknowledgement or the reverse, and claim statuses stop making sense without any error being raised. On one platform we reviewed, routing on ST01 alone had not caused harm yet only because every 277 it had received was a 277CA; the first payer status response would have been misread.

Here is what the two headers look like in public sample files: a Medicare contractor's 277CA example and a payer's 277 status response example.

277CA (acknowledgement):
GS*HN*...*005010X214~
ST*277*0001*005010X214~
BHT*0085*08*19157*20190606*123747*TH~

277 (claim status response):
GS*HN*...*005010X212~
ST*277*5579*005010X212~
BHT*0010*08*1001*20100901*1200*DG~

A minimal router in Python looks like this. It splits segments on the terminator and elements on the separator declared in the ISA, so it does not assume "*" and "~".

def route(x12: str) -> str:
    elem = x12[3]                      # element separator from ISA
    term = x12[105]                    # segment terminator after ISA16
    segs = [s.strip().split(elem) for s in x12.split(term) if s.strip()]
    st = next(s for s in segs if s[0] == "ST")
    gs = next(s for s in segs if s[0] == "GS")
    if st[1] == "999":
        return "ack_999"
    if st[1] == "835":
        return "remit_835"
    if st[1] == "271":
        return "eligibility_271"
    if st[1] == "277":
        bht = next(s for s in segs if s[0] == "BHT")
        bht06 = bht[6] if len(bht) > 6 else ""
        version = gs[8]
        if bht06 == "TH" and version.startswith("005010X214"):
            return "claim_ack_277ca"
        if bht06 == "DG" and version.startswith("005010X212"):
            return "claim_status_277"
    return "quarantine"                # unknown: hold it and alert a person

Claim status: you have to send a 276

Payer claim status does not arrive by itself. The 999, 277CA and 835 come to you unprompted, but to learn where a claim stands at the payer you must send a 276 claim status request and read the 277 that answers it.

Without this loop, a claim the payer is sitting on looks the same as a claim that is fine until someone notices it is old. The platform in our case study never sent a 276 and learned status only from what arrived on its own. Build the loop from the start:

  1. Each day, select claims accepted on a 277CA with no 835 after the number of days you expect for that payer.
  2. Send a 276 for each, through the same clearinghouse connection.
  3. Read the STC segment of the 277: category A codes are acknowledgements, P codes pending, F codes finalized. P3 means the payer is waiting on information from you.
  4. Update the claim's status by rank, so a weaker, later message never overwrites a rejection or a payment, and keep the history.

Our guide to 277 claim status automation covers the worklist side.

Posting the 835: CAS, reversals and balances

The 835 explains each payment and adjustment with CAS segments, and it can also take money back. Three details caused most of the posting errors we have fixed.

  • CAS is read as triplets. A CAS segment has one group code (CO, PR, OA or PI) followed by up to six sets of reason code, amount and quantity. A loop that expects a group code at the start of every set reads a quantity as a group code from the second adjustment on.
  • Reversals are claims too. CLP02 = 22 marks a reversal of a previous payment, with the original amounts negated. It is usually followed by the corrected claim payment. Post both, linked by the payer claim number in CLP07.
  • Balance math. Remaining balance is billed minus paid minus contractual (CO) adjustments. The patient's share is the PR adjustments. Charge minus paid makes the patient look like they owe the write-off. For example, on a $200 charge with a $150 contracted rate where the payer pays $120, the patient owes $30 (PR) and $50 is a contractual write-off (CO). The collectible balance is $30, not $80. The business cost of getting this wrong is covered in the real cost of getting claims integration wrong.
def read_cas(seg: list[str]) -> list[dict]:
    # seg = ["CAS", group, reason1, amount1, qty1, reason2, amount2, qty2, ...]
    group, rest = seg[1], seg[2:]
    out = []
    for i in range(0, len(rest), 3):
        reason = rest[i] if i < len(rest) else ""
        if not reason:
            break
        amount = float(rest[i + 1]) if i + 1 < len(rest) and rest[i + 1] else 0.0
        qty = rest[i + 2] if i + 2 < len(rest) else ""
        out.append({"group": group, "reason": reason, "amount": amount, "qty": qty})
    return out

To see a real file, read our 835 file example. To compare tools that automate this, see our review of payment posting software.

Testing and go-live checklist

Test the unhappy paths on purpose, because production will send them. A rejected 277CA, a denied claim and a reversal on an 835 are the three cases we always run before the first production file.

CheckDone when
EnrollmentClaims, ERA and EFT enrollment confirmed for every payer and provider going live
NetworkProduction IP addresses allowlisted; test and production credentials and folders confirmed separately
SecurityHost key pinned; credentials in a secrets store and absent from logs (check a real log line)
IdentifiersSender and receiver IDs in ISA and GS confirmed with Waystar in writing
EnvelopesISA15 = T on test files and P in production; unique ISA13 and GS06 on every file
ClaimsCLM01 unique and stable across retries; billing tax ID and NPIs correct; a deliberate double submission is blocked
StorageEvery response file saved in your own storage with the claim it matched
Routing999, 277CA, 277, 271 and 835 routed by content; unknown files quarantined with an alert
Status276 loop running; STC category codes mapped to actions
PostingMulti-reason CAS, PR and CO adjustments and a CLP02 = 22 reversal all post correctly
MonitoringAlert when a file has no 999 or 277CA within its expected window; silence is a failure

Go live in steps: one claim, followed through every response, then a small batch for one payer, then widen. The first production claims surface enrollment gaps and payer rules that test files do not.

One go-live trap we have seen: a payer whose ERA enrollment never completed looks like a payer that "never pays", when it is paying on paper. Check ERA enrollment before you open a denial investigation. Our article on EDI testing for clearinghouse integration covers the test plan, and our free EDI Inspector explains any 999, 277CA or 835 segment by segment.

Running it: the numbers to watch

A Waystar integration is finished when your team can answer these questions on demand. Most failures in claims are quiet: a claim sits in a state nobody watches until the filing deadline passes.

  • Claims waiting to be sent: anything here is unbilled care.
  • Failed and abandoned uploads: claims that never reached Waystar.
  • First-pass acceptance rate: claims accepted on the 277CA without rework, the best single signal of upstream data quality.
  • Claims with no payer answer past their expected window, which feed the 276 loop.
  • Denials by payer and reason: they cluster, and clusters are fixable. Many trace back to eligibility; see why claims get denied before they are sent.
  • Unmatched remittances: money received but not yet tied to a claim.

Symptoms and their usual causes

SymptomUsual cause
Connection refused from production but works from another serverThat server's IP address is not allowlisted
File uploaded but no 999 comes backWrong file extension or naming for your companion guide, or the file was overwritten by a later upload with the same name
Claims accepted, then denied by the payerEnrollment incomplete for that provider and payer
Statuses stale; the Waystar portal disagrees with your system277 routed on ST01 without checking BHT06, no 276 loop, or claims edited inside the portal
Duplicate claims at the payerControl numbers regenerated on retry
Receivables higher than anything ever collectedContractual (CO) adjustments counted as money owed

For the same patterns without Waystar specifics, read our clearinghouse integration guide and the seven claims integration traps.

What we observed vs what to confirm with Waystar

Everything above about Waystar's folders, file names, contract and API is what we saw on one integration. Before you build, confirm each point with Waystar and in your own companion guide.

We observedConfirm with Waystar
SFTP batch only; the API was a separate productWhich transactions your contract includes by API, and developer portal access
Files from Waystar carried ZIRMED as the ISA06 sender IDThe sender and receiver IDs for your account
XML portal reports arrived next to the X12 filesTheir format, and whether you can turn them on or off
Upload, Download and Archive folders; upload was write-onlyFolder paths for test and production
File extension set the file type (.CLP, .CLI, .CLD, .ELG)The current naming rules in your companion guide
IP allowlisting took daysThe current lead time and the form to request it
Payer enrollment took weeks, per payer and transactionWhich of your payers need enrollment or test files before go-live

Need help with your Waystar integration?

We build and repair clearinghouse connections inside EHRs and billing platforms: 837 generation, 999 and 277CA handling, 276 polling and 835 posting. If your integration "works" but claims keep going quiet or balances do not add up, the files coming back are usually where the money leaks. See our x12 edi integration services or talk to our team.

Ready to scale?

Talk to our healthcare engineering team about building, integrating, and shipping faster.

Frequently Asked Questions

Is Waystar a clearing house?

Yes. Waystar works as a healthcare clearinghouse: on our connection it received our 837 claim files over SFTP and returned 999 file acknowledgements, 277CA claim acknowledgements and 835 remittances. Waystar also sells wider revenue cycle software.

Does Waystar have public API documentation?

Waystar runs a developer portal at developer.waystar.com, linked as Developer Login from waystar.com. The home page is public and lists REST APIs, web services, SFTP, HL7, batch, X12 EDI and RPA. The documentation pages ask you to register and log in (checked November 4, 2026). What you can call in production depends on your contract.

What is ZirMed, and is it part of Waystar?

ZirMed was a revenue cycle technology company. Navicure and ZirMed merged in November 2017 and the combined company took the name Waystar in February 2018, according to Waystar's announcement. ZirMed is now part of Waystar.

Why do Waystar files say ZIRMED?

On our connection, the X12 files Waystar sent us carried ZIRMED as the sender ID in the ISA envelope (ISA06, qualifier ZZ), and its XML portal reports referenced a zirmed.com stylesheet. It is a carry-over from ZirMed. Confirm the sender and receiver IDs for your own account in writing.

What is the difference between a 277 and a 277CA?

Both start with ST*277. The 277CA (005010X214, BHT06 = TH) is an acknowledgement saying whether each claim was accepted into processing. The 277 claim status response (005010X212, BHT06 = DG) is the payer's answer to a 276, saying where the claim is in adjudication. Route them by BHT06 and the version in GS08.

How do I check claim status through Waystar, by API or 276?

Send a 276 claim status request and read the 277 response, through SFTP batch or through the API if your contract includes it. Payer status does not arrive on its own, so run a daily loop for claims that have gone quiet and act on the STC category code: A for acknowledgements, P for pending, F for finalized.

Where is the Waystar payer ID list?

Waystar's developer portal lists a Payer ID List page under Integration Basics, behind the portal login. Use the list for your own account, because payer IDs and the transactions each payer supports can differ by enrollment.

How long does Waystar clearinghouse enrollment take?

On our project, payer enrollment took weeks and was paperwork rather than engineering. It is per payer, per provider and per transaction type, with claims, ERA and EFT enrolled separately, and some payers ask for test files first. Start it, and the IP allowlist request, before you write code.
Share