Webhooks
i-screen sends HTTP POST requests to your configured partner endpoints when specific events occur.
Please contact i-screen with your webhook URLs for both sandbox and production environments before going live.
Verifying webhook requests
Every webhook request includes an Authorization header:
Authorization: Bearer <Partner's token>
This is the same partner token you use to authenticate to the API (see Authentication), sent by itself, without a practitioner token. Check this header against your known partner token to confirm a request genuinely came from i-screen before processing it.
Event overview
| Event | webhookUri field | type in payload | Fires from | Notes |
|---|---|---|---|---|
| Request form is ready | formReady | form-ready | Invoice settlement, or an order placed via the Partner API | Fires once per test, not once per order/invoice. An order or invoice with 3 form-generating tests fires 3 separate form-ready calls, one per test. Test kits never fire this (see caveat below). |
| Results are ready | resultsReady | results-ready | A test's results are marked available | Fires once per test, same as above. |
| Invoice settled notification | invoiceSettled | invoice-settled | An invoice is paid. This includes invoices you create via POST /invoices — the patient settles it on the practitioner portal's invoice page, and that settlement is what fires the webhook | Fires once per invoice, consolidating every test under it, whether or not each individual test generated a request form. |
| Order ready notification | orderReady | order-ready | An order is placed via the Partner API | Fires once per order, consolidating every test under it, same as invoice settled. |
| Patient link request approved | patientLinkRequestApproved | patient-link-request-approved | A patient approves a connection request | Fires once per approval. |
On type: every event now includes a type field in its payload, so if you point more than one webhookUri.* field at the same receiving endpoint, you can dispatch on type directly rather than inferring the event from payload shape. See the changelog for when this landed.
Per-test vs per-order/invoice: formReady and resultsReady operate at the individual test level, so a single order or invoice containing multiple tests can trigger several calls to the same webhook URL, one per test. invoiceSettled and orderReady operate at the order/invoice level instead: regardless of how many tests are in it, or how many of those generated a request form, you get exactly one call with every test listed inside it.
A caveat worth knowing if you use the practitioner portal alongside the Partner API for the same practitioner account: the practitioner portal has two separate order-creation paths, a direct-purchase flow and an invoice-payment flow. Only the invoice-payment flow currently sends Request form is ready. This is a practitioner-portal-only gap and does not affect orders placed via the Partner API, which always sends this webhook. We are deciding whether to close this gap; noting it here so it isn't a surprise if you're also watching practitioner-portal activity for the same account.
Request form is ready
Sent after an invoice is settled or an order is made and a test's request form is available. Called separately for each test, not once per order or invoice. Test kits do not generate request forms and will not trigger this webhook.
If an order or invoice contains 3 tests and all 3 generate a request form, you'll receive 3 separate form-ready calls, one per test. If one of those 3 is a test kit, you'll only receive 2. Compare this with Order ready / Invoice settled, which consolidate the whole order or invoice into a single call regardless of how many tests are inside it.
Payload
| Parameter | Description |
|---|---|
type string | Always "form-ready" |
invoiceId string | The invoice's record ID (if referred from an invoice) |
orderNumber string | The order number (if purchased directly) |
test.\_id string | The test's record ID |
test.title string | The test's title |
test.requestForm string | The request form URL |
{
"type": "form-ready",
"invoiceId": "6253a3e470f8f41f66b1f1ee",
"test": {
"_id": "610ad1070ae7ef00115c0a25",
"title": "Well Man Check",
"requestForm": "https://iscreen.s3.ap-southeast-2.amazonaws.com/sandbox/requestForms/..."
}
}
Trigger: Settle Invoice (staging only)
Settles an active invoice, generates tests, and triggers the Request form is ready webhook for each applicable test.
POST /triggers/settle
| Parameter | Required | Description |
|---|---|---|
invoiceId string | Yes | The invoice's record ID |
curl -X POST https://partner-staging.i-screen.com.au/v1/triggers/settle \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer f3ee05b1e3d140a3b17456fb69d28cd9-5f6a46513aa48d0bf80a04c7d52c9805633070a7803d3bd4"
{ "invoiceId": "6253a3e470f8f41f66b1f1ee" }
You can also re-fetch from the tests endpoints to retrieve request form URLs without relying on webhooks.
Results are ready
Sent when a test's status changes to results-available.
Payload
| Parameter | Description |
|---|---|
type string | Always "results-ready" |
testId string | The test's record ID |
patientId string | The patient's record ID |
result object | The test's result object |
attachments array | The test's attachments |
result.doctor (the reviewing doctor's firstName, lastName, qualifications, avatar, title) is only present when a doctor has been assigned to the result.
{
"type": "results-ready",
"testId": "62680e5a183a67d280bf29eb",
"patientId": "624a51c9ca695bf5c7e4b1fc",
"result": {
"segments": [
{
"label": "Male Hormone Panel",
"observations": [
{
"label": "Testosterone",
"description": "Testosterone is an anabolic hormone responsible for bone and muscle strength, as well as mood, energy and sexual function.",
"value": "3",
"units": "nmol/L",
"range": { "min": 10, "max": 35 }
}
]
}
],
"recommendations": [
{
"title": "Optimise your lifestyle",
"category": "heart",
"comment": "<p>Taking steps to improve your diet...</p>"
}
],
"comment": "<p>Congratulations on taking the Male Hormone Check!</p>",
"doctor": {
"firstName": "Raj",
"lastName": "Joshi",
"qualifications": "MBChB, MRCGP, Dip Mtn Med, Dip FIPT, FRGS",
"avatar": "https://d1rhkdlqb7psvs.cloudfront.net/assets/img/doctors/dr-raj-joshi-i-screen.jpg",
"title": "Dr"
},
"retest": { "value": 3, "period": "months" },
"availableDate": "2022-04-27T13:22:23.554Z"
},
"attachments": [
"https://iscreen-dev.s3.eu-west-1.amazonaws.com/attachments/..."
]
}
Trigger: Results Are Ready (staging only)
Sets a test's status to results-available and triggers the Results are ready webhook.
POST /triggers/results
| Parameter | Required | Description |
|---|---|---|
testId string | Yes | The test's record ID |
curl -X POST https://partner-staging.i-screen.com.au/v1/triggers/results \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer f3ee05b1e3d140a3b17456fb69d28cd9-5f6a46513aa48d0bf80a04c7d52c9805633070a7803d3bd4"
{ "testId": "622460e7e173b40012c4b51e" }
Invoice settled notification
Sent after an invoice is settled. All tests under the invoice are consolidated into a single webhook call, regardless of how many tests it contains or how many of them individually generated a request form.
This fires for any invoice once it's paid, including ones you create via POST /invoices. Creating an invoice through the API doesn't settle it, the patient does that by paying on the invoice link (practitioner.i-screen.com.au/invoice/:id); that payment is what triggers this webhook.
Payload
| Parameter | Description |
|---|---|
type string | Webhook type: invoice-settled |
invoiceId string | The invoice's record ID |
tests array | Array of test objects |
Test object
| Parameter | Description |
|---|---|
_id string | The test's record ID |
title string | The test's title |
requestForm string | Request form URL (if applicable) |
mailerInstruction string | Instruction HTML (if applicable) |
{
"type": "invoice-settled",
"invoiceId": "649546568f0a484f80f3aa10",
"tests": [
{
"_id": "649578aba5d3cb61bf81e228",
"title": "Advanced Lipid Check",
"requestForm": "https://iscreen-dev.s3.eu-west-1.amazonaws.com/requestForms/..."
},
{
"_id": "649578aba5d3cb61bf81e259",
"title": "Platinum Health Check - Male",
"requestForm": "https://iscreen-dev.s3.eu-west-1.amazonaws.com/requestForms/...",
"mailerInstruction": "<p>There are two components to the Platinum Health Check...</p>"
},
{
"_id": "649578aba5d3cb61bf81e1fc",
"title": "Iodine Loading Check",
"mailerInstruction": "Your test kit is on its way and you'll receive this in the mail in the next 2-3 business days."
}
]
}
Order ready notification
Sent after a direct purchase order is made. All tests under the order are consolidated into a single webhook call, regardless of how many tests it contains or how many of them individually generated a request form.
Payload
| Parameter | Description |
|---|---|
type string | Webhook type: order-ready |
orderNumber string | The order number |
tests array | Array of test objects (same structure as invoice settled) |
{
"type": "order-ready",
"orderNumber": "ORD067921",
"tests": [
{
"_id": "662dfc945d7cd6e3bcc2eab2",
"title": "Vitamin B12 Check",
"requestForm": "https://iscreen-dev.s3.eu-west-1.amazonaws.com/staging/requestForms/..."
},
{
"_id": "662dfc945d7cd6e3bcc2eac7",
"title": "Biological Age Check",
"mailerInstruction": "<p>Your test kit is on its way...</p>"
}
]
}
Patient link request approved
Sent when a patient approves a connection request created via POST /patients/link-requests. Configure this event's URL separately via webhookUri.patientLinkRequestApproved on your partner record.
There is no rejection webhook. If a patient does not respond, the link request simply expires after 7 days; no webhook is sent for expiry.
Payload
| Parameter | Description |
|---|---|
type string | Always "patient-link-request-approved" |
linkRequestId string | The link request's record ID |
patientId string | The patient's record ID, now available to your practitioner |
practitionerId string | Your practitioner's record ID |
status string | Always approved |
approvedAt string | When the patient approved |
{
"type": "patient-link-request-approved",
"linkRequestId": "610ad2a60ae7ef00115c0aa3",
"patientId": "610ad1070ae7ef00115c0a25",
"practitionerId": "6253a3e470f8f41f66b1f1ee",
"status": "approved",
"approvedAt": "2026-08-01T02:00:00.000Z"
}
See What happens when a patient approves for the side effects on the patient's account when this fires.
Trigger: Simulate Link Request Approval (staging only)
Approving a link request normally requires the patient to click a link in an email and log in to their own i-screen account. Since a sandbox patient may not have real i-screen credentials, this trigger performs the same approval and fires the same webhook, without needing the patient to act.
POST /patients/link-requests/:linkRequestId/simulate-approval
URL Parameters
| Parameter | Description |
|---|---|
linkRequestId | The link request's record ID |
No request body is required.
curl -X POST https://partner-staging.i-screen.com.au/v1/patients/link-requests/610ad2a60ae7ef00115c0aa3/simulate-approval \
-H "Authorization: Bearer f3ee05b1e3d140a3b17456fb69d28cd9-5f6a46513aa48d0bf80a04c7d52c9805633070a7803d3bd4"
Response
{
"success": true,
"message": "Acme Health successfully linked to this patient."
}
If the request has already expired, you'll get a 400 Bad Request. If your practitioner is already linked to the patient, success is still true with alreadyLinked: true and no webhook is sent (approval is one-time per patient/practitioner pair).