Two different status fields
The API returnsstatus in two places and they describe different things.
A referral with two visits tracks each one separately, so a referral can have
one
booked visit and one still waiting on authorization.
The visit lifecycle
Not every referral takes every hop. The gates beforeready_to_book depend on
the line of business, and a visit can leave the happy path at any point.
Before booking
Scheduled
At and after the exam
Other outcomes
Which statuses to act on
Three moments usually matter to an integration:booked— tell the patient where and when. Readvisits[].locationandvisits[].start_timefrom the same payload.report_uploaded— a report exists. Pull the PDF with GET /api/v2/order_documents/report.sent_to_rp— someone on your side needs to unblock the referral.
invoice_ready means reporting is finished for that visit. It is not
the signal to pull payments. Payments are a referral-level concern: once the
top-level status is no longer pending or canceled, partners with payment
read enabled can keep listing them with
GET /api/v2/referrals/{referral_id}/payments.
New payments can arrive months later. The workers’ compensation example is on
Workers’ compensation and payers.
Treat the rest as progress information you can surface but should not gate on.
Statuses never move backwards through the reporting phases, so you can store the
latest one you have seen. New statuses may be added over time, so ignore any
string you do not recognize rather than failing.
Referral-level status
The top-levelstatus tracks the case commercially rather than clinically.
These are the values with a meaning outside Scan:
Other values may appear on this field. They are internal accounts-receivable
states and carry no integration meaning, so do not branch on them.
invoice_ready appears at both levels but means different things. On
visits[].status, reporting is complete for that visit. On the top-level
status, the referral’s case is ready for billing.
Payment pulls belong on the referral-level field. Skip pending and canceled.
invoice_ready, invoice_sent, closed, and the other accounts-receivable
values are when listing payments makes sense, and new rows can still appear
months after the first invoice-ready event.
Receiving changes
You have two options, and they work well together: subscribe to the webhook for push updates, and poll when you need a snapshot on demand.Webhook
We POST to thewebhook_url on your api_credentials record every time a visit
status changes. The full payload is on
Status changed, and Webhooks
covers the event catalog, retries, and delivery ordering across all three
events.
Verify the signature
Read the request body as raw bytes before parsing JSON. Compute HMAC-SHA256 over"{timestamp}.{raw_body}" with the signing secret shown on the API access tab,
then compare it to the digest after v1= using a constant-time comparison.
Reject timestamps older than five minutes to limit replay attacks.
event_id, the new status, a changes.status array
of [previous, current], the visit_id, an occurred_at timestamp, and a full
referral snapshot so you rarely need a follow-up call.
event_id to discard duplicates.
Polling
Ask for the current state whenever you need it.Notification delivery
Separately from visit status, you can track the emails and texts Scan sends the patient about a referral, such as intake questions, booking confirmations, and appointment reminders. This is delivery metadata only. Recipients, subjects, message bodies, and provider failure text are never returned on either route. Both routes are off by default and are enabled independently of each other and of visit-status delivery, so you can take one without the other. Ask Scan to turn on what you need:Delivery statuses
pending means we have queued the message and dispatched means we have handed
it to the email or SMS provider. Everything after that comes from the provider’s
own callbacks, so the two channels do not share a vocabulary.
How far a message gets depends on what the provider reports back, so not every
notification reaches every status. As with visit status, ignore any value you do
not recognize rather than failing.
Webhook
Notification events go to the samewebhook_url and are signed the same way, so
the verification code above works unchanged. The event
header is what differs:
You receive one event when the notification is created and one for every status
change after it. On that first event
changes.status is [null, "pending"].
event_id.
Full payload:
Notification status changed.
Pull the history
Ask for every notification attached to a referral and its visits, newest first.visit_id is set when the message is about a specific visit and null when it
is about the referral as a whole. Results are paginated with page and
per_page, which defaults to 20.
