DrCare247 Webhooks
Webhooks send updates about DrCare247 members, appointments, and lab orders to your registered endpoint. Each event arrives as an HTTP POST with a JSON body, so your application can respond without repeatedly polling.
Notifications contain identifiers and status information, not clinical data. Use those identifiers to fetch details through Get Consultation or Get Lab Result.
- 1Setting upAt onboarding
- 2EventsWhat you'll receive
- 3The requestHeaders and envelope
- 4PayloadsMember, appointment, lab
- 5Verify the signatureHMAC-SHA256
- 6Delivery and retriesBuild your receiver
Setting up
DrCare247 configures webhook destinations during onboarding. Provide:
| Item | Notes |
|---|---|
| Endpoint URL | A public HTTPS URL that accepts a POST with a JSON body. |
| Signing secret | A random string you generate (32 or more characters recommended). Share it with us securely. DrCare247 uses it to sign every notification. Strongly recommended: without it, notifications are sent unsigned. |
| Receiver name | A label of your choice. It is sent back as receiverName, but with two endpoints it isn't reliable per endpoint, so don't use it for routing. |
An account can have up to two endpoints. Every endpoint receives every event; you can't subscribe to individual events. You receive notifications only for members linked to your account, and only after DrCare247 has turned webhooks on for your account.
Events
Review the five webhook event types below to determine which notifications your receiver should process.
In these docs, a member is a patient record in DrCare247.
| Event | Sent when |
|---|---|
MemberCreated | A member is created: through the API, through SSO when a new patient signs in, or when a patient is added in the DrCare247 portals (except bulk import) |
MemberUpdated | A member's profile is edited, through the API or the portal profile screens. Some changes, such as bulk activation or deactivation, don't send it, so reconcile with Get Member. |
AppointmentCreated | An appointment is confirmed: booked and, where payment is required, paid |
AppointmentUpdated | Something about the appointment changes after that: rescheduled, in progress, completed, cancelled, denied, no-show, addendum, provider reassigned, and so on. The status may be unchanged. |
LabOrderCompleted | A lab order is completed and its results are ready to fetch |
There is no separate AppointmentCancelled event. A cancellation arrives as AppointmentUpdated with "status": "Cancelled".
The request
Each delivery includes headers that identify the attempt and, when configured, help you verify the signature.
POST /your/webhook/path HTTP/1.1
Content-Type: application/json; charset=utf-8
X-Webhook-ID: 022e7cd5-f2d8-4ec7-9e5b-17700696e954
X-Webhook-Timestamp: 1792040923
X-Webhook-Signature: q3V0c0v2kz1oJ9mY2Xo3u7m2b3a8m2nH8Vq1x9Yk0sA=
| Header | Sent | Meaning |
|---|---|---|
X-Webhook-ID | Always | A unique id for this delivery attempt. A retry of the same event gets a new id, so don't use it to detect duplicates. |
X-Webhook-Signature | Only when a signing secret is set | Base64(HMAC-SHA256(secret, raw request body)). See Verifying the signature. |
X-Webhook-Timestamp | Only when a signing secret is set | When this attempt was sent, in Unix time (seconds). It isn't covered by the signature, so don't rely on it to block replayed requests. |
Envelope
Every event has the same outer structure, called the envelope. The event-specific fields are inside data.
{
"eventType": "AppointmentCreated",
"timestamp": "2026-10-15T05:08:43.4573232Z",
"version": "1.0",
"accountId": "72ca7262-64d7-412d-986b-a9421040105b",
"receiverName": "YourReceiverName",
"data": { }
}
| Field | Meaning |
|---|---|
eventType | One of the five event names in Events |
timestamp | When the event was raised, in UTC. It stays the same on retries. |
version | Currently "1.0". For information only; don't branch on it. |
accountId | The DrCare247 account this notification is for |
receiverName | The receiver name registered for one of your endpoints, or null if none is registered. With two endpoints it may carry the other endpoint's name. Don't use it for routing or security. |
data | The event payload. See the sections below. |
A member can be shared with more than one DrCare247 account. Each of those accounts' endpoints then receives the event, each with its own accountId. The data is identical for every recipient. Your own references in it (RefId, memberRefId, extLabOrderRefId) are looked up in the account where the change happened, and ExtRefId is the reference stored on the appointment when it was booked, so they may not be yours. For shared members, match on the DrCare247 ids instead: MemberId, consultationId or LaborderId.
Member events use PascalCase (MemberId). Appointment events use camelCase, except ExtRefId. The lab event mixes both: LaborderId and Status are PascalCase, extLabOrderRefId is camelCase. Use the names exactly as shown on this page.
Member events
When a patient is added, or their profile or status changes, use this payload to keep your patient records in step with DrCare247.
MemberCreated and MemberUpdated have the same payload.
| Field | Type | Meaning |
|---|---|---|
MemberId | UUID | DrCare247 member id. Use it to match the member on your side. |
MemberNo | string | Human-readable member number, for example P100475 |
CreatedOn | string (ISO 8601, UTC) | When the change in this event was made. On MemberUpdated, this is the update time, not the creation time. |
RefId | string | null | Your patient id: the refId you sent to Create Member, or the user_id from your SSO token if SSO created the member. It is null on events raised from some portal screens (for example, a patient editing their own profile), even when DrCare247 holds your id, so always match on MemberId. |
ParentId | UUID | null | For a dependent, the MemberId of the primary member. null for a primary member. |
Status | string | Active or Inactive |
{
"eventType": "MemberCreated",
"timestamp": "2026-10-14T11:00:41.8456858Z",
"version": "1.0",
"accountId": "72ca7262-64d7-412d-986b-a9421040105b",
"receiverName": "YourReceiverName",
"data": {
"MemberId": "ba1453dd-b465-478e-8e5f-10d5f97eafd8",
"MemberNo": "P101973",
"CreatedOn": "2026-10-14T11:00:40.9113025Z",
"RefId": "PM-150302",
"ParentId": null,
"Status": "Active"
}
}
Appointment events
When a booking is confirmed or its status later changes, for example a cancellation, use this payload to update the matching order or appointment on your side.
AppointmentCreated and AppointmentUpdated have the same payload. Each notification names two linked records: the appointment (the booking itself) and the consultation (the visit record that holds the outcome).
| Field | Type | Meaning |
|---|---|---|
consultationId | UUID | DrCare247 consultation id. Pass it as Id to Get Consultation (GET /api/v1/Encounter/get). |
consultationNo | string | Human-readable consultation number, for example E108873 |
appointmentId | UUID | DrCare247 appointment id. Pass it as scheduleId to Get Consultation, or use it with Reschedule. |
ExtRefId | string | null | Your order or appointment reference: the extRefId you sent to Create Appointment, or the refid on your SSO link. Note the capital E. |
memberId | UUID | DrCare247 member id of the patient |
memberNo | string | Human-readable member number |
memberRefId | string | null | Your patient id, when DrCare247 has one for this member. Same rules as RefId in Member events. |
status | string | The appointment status: Requested, Pending, Scheduled, InProgress, Completed, Cancelled, NoShow, Denied or Addendum. Other values (Abandoned, PendingConfirmation) can appear. Treat any value you don't recognise as "something changed" and check Get Consultation. |
scheduledOnUtc | string | The appointment date and time in UTC. It has no Z or offset suffix; treat it as UTC. |
AppointmentCreated for a "consult now" booking that is waiting for a provider:
{
"eventType": "AppointmentCreated",
"timestamp": "2026-10-15T05:08:43.4573232Z",
"version": "1.0",
"accountId": "72ca7262-64d7-412d-986b-a9421040105b",
"receiverName": "YourReceiverName",
"data": {
"consultationId": "fad9c81e-631f-42f4-82bc-7c7735713631",
"consultationNo": "E108873",
"appointmentId": "9836d6f9-bcdd-4d08-9765-add03dd25cbe",
"ExtRefId": "78-C99626",
"memberId": "fd8c75dd-1e13-422c-800b-cebc3541666e",
"memberNo": "P100906",
"memberRefId": null,
"status": "Requested",
"scheduledOnUtc": "2026-10-15T05:04:56"
}
}
AppointmentUpdated after a cancellation:
{
"eventType": "AppointmentUpdated",
"timestamp": "2026-10-17T06:21:00.1091432Z",
"version": "1.0",
"accountId": "72ca7262-64d7-412d-986b-a9421040105b",
"receiverName": "YourReceiverName",
"data": {
"consultationId": "cf1658af-f3d1-4a14-a137-4879ad1063e8",
"consultationNo": "E100612",
"appointmentId": "01699bce-1023-4243-8c04-a5e5c50c57d2",
"ExtRefId": "EXT-APT-10001",
"memberId": "e9aee5fa-8f94-4191-a177-35658a4a105d",
"memberNo": "P100494",
"memberRefId": "5408",
"status": "Cancelled",
"scheduledOnUtc": "2026-10-16T17:52:00"
}
}
Lab order completed
When a lab order's results are ready, this notification tells you which order to fetch with Get Lab Result.
| Field | Type | Meaning |
|---|---|---|
LaborderId | UUID | DrCare247 lab order id (note the lowercase o). Pass it as orderId to Get Lab Result. |
extLabOrderRefId | string | null | Your extOrderRefId from Create Lab Order, or null if you didn't send one |
Status | string | Always Completed |
When final results are critical, a provider must review them first; until then, Get Lab Order shows Pending Review. This event is sent after the provider signs them off.
If the lab later sends corrected results, the event can arrive again for the same LaborderId. Fetch the results again each time.
{
"eventType": "LabOrderCompleted",
"timestamp": "2025-10-09T06:28:24.5082021Z",
"version": "1.0",
"accountId": "72ca7262-64d7-412d-986b-a9421040105b",
"receiverName": "YourReceiverName",
"data": {
"LaborderId": "cb3cca2c-bb9f-451a-9b8b-12f21427ddc8",
"extLabOrderRefId": "LO-00001",
"Status": "Completed"
}
}
Verifying the signature
Before processing a notification, confirm that it came from DrCare247 and has not been modified. If you configured a signing secret, verify each request:
- Read the raw request body as bytes, before any JSON parsing.
- Compute
Base64(HMAC-SHA256(key = your secret as UTF-8 bytes, message = raw body)). Use standard Base64 (with+,/and=), not hex. - Compare the result with the
X-Webhook-Signatureheader using a constant-time comparison. Don't includeX-Webhook-Timestampin the calculation. - If they don't match, or the header is missing, reject the request (for example with
401).
Always sign the raw bytes, never JSON you parsed and serialized again: even a change in spacing or key order breaks the signature. In Flask, use request.get_data(). In Express, use express.raw({ type: "application/json" }) on the webhook route so req.body is the raw Buffer.
import base64, hashlib, hmac
def is_valid(raw_body: bytes, signature: str, secret: str) -> bool:
expected = base64.b64encode(hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).digest()).decode()
return hmac.compare_digest(expected, signature or "")
const crypto = require("crypto");
function isValid(rawBody, signature, secret) {
const expected = Buffer.from(crypto.createHmac("sha256", secret).update(rawBody).digest("base64"));
const received = Buffer.from(signature || "");
return expected.length === received.length && crypto.timingSafeEqual(expected, received);
}
Test your endpoint with curl
Send this example signed MemberCreated notification to test your receiver and signature verification:
SECRET='<your signing secret>'
BODY='{"eventType":"MemberCreated","timestamp":"2026-10-14T11:00:41.8456858Z","version":"1.0","accountId":"72ca7262-64d7-412d-986b-a9421040105b","receiverName":"YourReceiverName","data":{"MemberId":"ba1453dd-b465-478e-8e5f-10d5f97eafd8","MemberNo":"P101973","CreatedOn":"2026-10-14T11:00:40.9113025Z","RefId":"PM-150302","ParentId":null,"Status":"Active"}}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64)
curl -X POST "https://your-endpoint.example.com/webhooks/drcare247" \
-H "Content-Type: application/json; charset=utf-8" \
-H "X-Webhook-ID: 00000000-0000-0000-0000-000000000001" \
-H "X-Webhook-Timestamp: $(date +%s)" \
-H "X-Webhook-Signature: $SIG" \
--data-binary "$BODY"
Your endpoint should return 200. To test rejection, change a character in BODY after calculating SIG and resend it. Your receiver should reject the modified request.
Delivery and retries
Design your receiver to handle timeouts, retries, duplicate notifications, and out-of-order delivery. The rules below describe DrCare247's delivery behavior.
- Respond quickly. Return any 2xx status within 30 seconds. DrCare247 ignores the response body, so an empty
200is ideal. Do your processing after you respond. - Don't redirect. Answer at the URL you registered. A redirect (
301/302) can turn thePOSTinto aGETwithout the body. - Failures are retried twice. Any other status, a timeout or a connection error counts as a failure. DrCare247 retries after about 2 seconds and again after about 4 seconds: 3 attempts in total. After the third failure, the notification is not sent again. Retries are best-effort and not guaranteed, so reconcile with the API as described below.
- Order isn't guaranteed, and duplicates happen. Notifications can arrive out of order, and the same event can arrive more than once (for example, after a timeout, or twice at the same moment for some appointments). When you need the current state, such as the latest appointment status, call the API (Get Consultation) instead of relying on the order of arrival.
- Processing is idempotent, meaning a repeated notification has no extra effect. There is no event id that stays the same across retries, so detect a repeated notification by its full
data(for exampleconsultationId+status+scheduledOnUtc), and always re-read the current state from the API. Don't useX-Webhook-ID. - Webhooks are a signal, not the record. Reconcile with the API regularly (Get Member, Get Consultation, Get Lab Order), so a missed notification doesn't leave your data out of date.
- Every request is verified. If you set a signing secret, reject any request with a missing or invalid signature.