Developers
Guest name and email on every invoice: what's new in the Onfire Platform API
October 28, 2026 · Onfire Health
Until now, an app connected to an Onfire Health practice could see guest details only on the invoices and Checkout Sessions it created itself. Invoices a practitioner made in the Onfire portal, or that another platform created, were either invisible or stripped of the guest. Many of you told us you needed the guest's name and email address to reconcile invoices with your other systems.
From October 28, 2026, that changes. Once a practice accepts the updated Partner Platform Terms of Service (version October 23, 2026), every app it has connected can:
- read every invoice of the practice with the guest's name and email, whichever app created it, or the portal;
- receive webhooks for the practice's invoices and Checkout Sessions, including ones other
apps created, with a new
clientblock carrying the guest.
This post explains what changes, when, and what you may need to do. The Onfire Platform Integration Guide has been revised to match, and is the full reference.
When it applies: practice by practice, then everyone
The change is switched on per practice:
- When a practice accepts the Terms. A practitioner accepts them in the Onfire partner portal, under Settings → Company, or when they're connecting an app. From that moment, everything below applies to that practice's records.
- On November 22, 2026 (midnight Pacific), the Terms bind every practice, accepted or not.
- Until a practice accepts, everything you receive for it stays exactly as it is today.
Your app can't accept on a practice's behalf. A platform serving many practices will have both kinds at once for a few weeks, so build for both, and decide per practice and per record from what Onfire sends you. Don't use a global switch.
| Practice hasn't accepted | Practice has accepted (every practice from Nov 22, 2026) | |
|---|---|---|
| Invoices your app created (default reads) | Whole record, guest included | Unchanged |
?scope=all | 403 | Every invoice of the practice, with guest name and email |
| Checkout Session read | client.name, client.email; client.phone only if your app created it | Unchanged |
| Webhooks for records your app created | No client key | Adds client: the whole guest, as you supplied it |
| Webhooks for records your app didn't create | None | Every invoice.* and checkout_session.* event, with client holding name and email |
Reading every invoice: ?scope=all
Both invoice reads (Guide §6.3) accept ?scope=all:
GET /api/v1/core/partner/invoices/?scope=all
GET /api/v1/core/partner/invoices/{invoice_id}?scope=all
The first lists every invoice of the practice. The second retrieves any one of them by its
invoice_id. Once the practice has accepted the Terms, ?scope=all needs only a connection
the practice hasn't revoked. Before then, it's a 403. Without ?scope=all, you get only the invoices your app created, with full
guest details, as today.
Every invoice in a ?scope=all response carries is_own:
is_own: true: your app created it. The record is whole.is_own: false: another app or the portal created it. It carriesclient_nameandclient_email.client_phone,client_billing_address,member_id,invoice_public_idandpay_urlare still present butnull, and so are the Checkout Session summary'spublic_id,success_urlandcancel_url. Identify these invoices byinvoice_id.
{
"invoices": [
{
"invoice_id": "00000000-0000-4000-8000-0000000000a1",
"invoice_public_id": null,
"is_own": false,
"status": "paid",
"amount": "2050.00",
"currency": "USD",
"client_name": "Example Guest",
"client_email": "guest@example.com",
"client_phone": null,
"client_billing_address": null,
"member_id": null,
"pay_url": null,
"external_invoice_ref": "their-invoice-ref-0001",
"rate_card_ref": "rc_fixture01",
"created_at": "2026-06-29T17:50:00Z"
}
],
"total": 1,
"page": 1,
"page_size": 100
}
Two things to watch:
- Reconcile your own invoices with the default scope. Under
?scope=all, anexternal_invoice_reffilter can also match another app's invoice that happens to use the same reference. References are unique only within your app. - A 403 on
?scope=allfor a practice that hasn't accepted isn't an error to retry. Fall back to the invoices your app created. If it helps your users, let the practitioner know they can accept the Terms in the Onfire portal.
Webhooks: a client block, and more events
Once a practice has accepted the Terms:
Each invoice.* and checkout_session.* payload gains a client object.
data.invoice.client is {name, email, phone, billing_address}, and
data.checkout_session.client is {name, email, phone}. On records your app created, it's the
whole guest as you supplied it. On anything else, phone and billing_address are null.
{
"id": "evt_…",
"type": "invoice.paid",
"api_version": "2026-06-01",
"created": 1782755598,
"data": {
"invoice": {
"partner_public_id": "prt_fixture00000000000000000000000001",
"invoice_public_id": "inv_fixture00000000000000000000000001",
"status": "paid",
"external_invoice_ref": "your-invoice-ref-0001",
"external_client_ref": "your-client-ref-0001",
"amount": "2050.00",
"currency": "USD",
"ref_id": "rc_fixture01",
"payment_reference": "pay_fixture0000000000000001",
"client": {
"name": "Example Guest",
"email": "guest@example.com",
"phone": "+15555550100",
"billing_address": {"line1": "1 Example Street", "line2": null, "city": "Springfield",
"state": "CA", "postal_code": "90000", "country": "US"}
}
}
}
}
client is absent, not null, for a practice that hasn't accepted. Its presence is the
signal that the practice has accepted. Inside it, a field your app may not see is null.
More events reach you. invoice.* and checkout_session.* events now go to every app the
practice has connected and that has an active endpoint, not only to the app that created the
record. They begin once Onfire has seen your app act for that practice: a record you created,
or any API call with the practice's token. Calling GET /api/v1/meta/partners/me right
after connecting is enough.
Tell your records from other apps'
This is the change most likely to need code. An event for a record your app didn't create
arrives at the same endpoint as your own, and it carries the other app's references:
its external_invoice_ref, external_client_ref and client_reference_id. Those can equal
values your app uses. If your receiver matches events on your own reference alone, another
app's invoice.paid could mark one of your invoices paid.
The sign is a null id:
| Event | Yours | Another app's | Key another app's record on |
|---|---|---|---|
invoice.* | invoice_public_id set | invoice_public_id: null, and invoice_id (UUID) present | invoice_id |
checkout_session.* | public_id set | public_id: null and invoice_public_id: null | id |
Here is another app's invoice event, as your endpoint receives it:
"invoice": {
"partner_public_id": "prt_fixture00000000000000000000000001",
"invoice_public_id": null,
"invoice_id": "00000000-0000-4000-8000-0000000000a1",
"status": "paid",
"external_invoice_ref": "your-invoice-ref-0001",
"external_client_ref": "your-client-ref-0001",
"amount": "2050.00",
"currency": "USD",
"ref_id": "rc_fixture01",
"payment_reference": "pay_fixture0000000000000001",
"client": {"name": "Example Guest", "email": "guest@example.com", "phone": null, "billing_address": null}
}
Store these events like any other (Guide §7.5): your events table is your history, even if your product doesn't use other apps' records yet.
What hasn't changed
- No version bump. The change is additive:
api_versionstays2026-06-01, and nothing existing changes shape. Make sure your parser accepts a new key (client) and new events without failing; Guide §11 already allows additive changes. - Subscriptions still apply.
checkout_session.*events reach an endpoint only if it subscribes to them by name. Email partner@onfirehealth.com if yours doesn't. subscription.*events are unchanged for now.- Signing and delivery (Guide §7.4, §7.5) are unchanged.
A Checkout Session change that applies to every practice
Whether or not a practice has accepted, a Checkout Session's guest phone now goes only to
the app that created the session. Name and email still reach every connected app. If your
app reads client.phone from Checkout Sessions it didn't create, it receives null from
October 28, 2026.
Revoking
When a practice that has accepted disconnects your app, you stop receiving its data at once,
including queued webhook deliveries that carry a client block (one queued before the
practice accepted has none, and can still arrive). For any practice, a ?scope=all read with a token
issued before the disconnect is a 403: The practitioner revoked this Connected App; they must connect it again. Reconnecting means a new consent, and a new token.
Your responsibilities
- Guest names and emails are personal data. Protect what you receive as the Partner Terms require (sections 2.4 and 7.5). Keep it out of logs and test data, and store only what your product needs. Remember that your stored webhook bodies contain it too.
- Don't put clinical or health information in
metadata, or in any other field you send us (section 7.5(b)). Once a practice accepts, a Checkout Session'smetadatareaches every app the practice has connected.
The Terms are at onfirehealth.com/partner-agreement, and that page is the authoritative version.
Checklist
- Accept a
clientkey oninvoice.*andcheckout_session.*events, and new event types, without failing. - Tell your records from other apps' by the
nullid, and key other apps' records oninvoice_idorid, never on theirexternal_*_reforclient_reference_id. - Use
?scope=allonly where you need other apps' invoices; reconcile your own with the default scope. - Handle a 403 on
?scope=allas "this practice hasn't accepted yet", not as a failure. - Stop reading
client.phonefrom Checkout Sessions your app didn't create. - Treat everything in
clientandclient_*as personal data, including in stored bodies. - Make sure Onfire has seen your app act for each practice, for example by calling
GET /api/v1/meta/partners/meafter connecting.
Building with an AI coding agent?
Version 0.3.0 of the Onfire partner integration skill covers all of this. It includes signed example deliveries for practices that have accepted, both for the app that created the record and for another app, so your receiver can be tested against them. Ask your Onfire contact for it. The Onfire Zapier app is also being updated to match.
Questions? Email partner@onfirehealth.com or your Onfire contact.
