Skip to main content

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.

  1. 1Setting upAt onboarding
  2. 2EventsWhat you'll receive
  3. 3The requestHeaders and envelope
  4. 4PayloadsMember, appointment, lab
  5. 5Verify the signatureHMAC-SHA256
  6. 6Delivery and retriesBuild your receiver

Setting up​

DrCare247 configures webhook destinations during onboarding. Provide:

ItemNotes
Endpoint URLA public HTTPS URL that accepts a POST with a JSON body.
Signing secretA 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 nameA 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.
info

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.

EventSent when
MemberCreatedA 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)
MemberUpdatedA 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.
AppointmentCreatedAn appointment is confirmed: booked and, where payment is required, paid
AppointmentUpdatedSomething 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.
LabOrderCompletedA lab order is completed and its results are ready to fetch
note

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.

Request headers
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=
HeaderSentMeaning
X-Webhook-IDAlwaysA 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-SignatureOnly when a signing secret is setBase64(HMAC-SHA256(secret, raw request body)). See Verifying the signature.
X-Webhook-TimestampOnly when a signing secret is setWhen 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.

Envelope
{
"eventType": "AppointmentCreated",
"timestamp": "2026-10-15T05:08:43.4573232Z",
"version": "1.0",
"accountId": "72ca7262-64d7-412d-986b-a9421040105b",
"receiverName": "YourReceiverName",
"data": { }
}
FieldMeaning
eventTypeOne of the five event names in Events
timestampWhen the event was raised, in UTC. It stays the same on retries.
versionCurrently "1.0". For information only; don't branch on it.
accountIdThe DrCare247 account this notification is for
receiverNameThe 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.
dataThe event payload. See the sections below.
Members shared across accounts

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.

Field names are case-sensitive and not uniform

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.

FieldTypeMeaning
MemberIdUUIDDrCare247 member id. Use it to match the member on your side.
MemberNostringHuman-readable member number, for example P100475
CreatedOnstring (ISO 8601, UTC)When the change in this event was made. On MemberUpdated, this is the update time, not the creation time.
RefIdstring | nullYour 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.
ParentIdUUID | nullFor a dependent, the MemberId of the primary member. null for a primary member.
StatusstringActive or Inactive
MemberCreated
{
"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).

FieldTypeMeaning
consultationIdUUIDDrCare247 consultation id. Pass it as Id to Get Consultation (GET /api/v1/Encounter/get).
consultationNostringHuman-readable consultation number, for example E108873
appointmentIdUUIDDrCare247 appointment id. Pass it as scheduleId to Get Consultation, or use it with Reschedule.
ExtRefIdstring | nullYour order or appointment reference: the extRefId you sent to Create Appointment, or the refid on your SSO link. Note the capital E.
memberIdUUIDDrCare247 member id of the patient
memberNostringHuman-readable member number
memberRefIdstring | nullYour patient id, when DrCare247 has one for this member. Same rules as RefId in Member events.
statusstringThe 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.
scheduledOnUtcstringThe 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:

AppointmentCreated
{
"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:

AppointmentUpdated
{
"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.

FieldTypeMeaning
LaborderIdUUIDDrCare247 lab order id (note the lowercase o). Pass it as orderId to Get Lab Result.
extLabOrderRefIdstring | nullYour extOrderRefId from Create Lab Order, or null if you didn't send one
StatusstringAlways 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.

LabOrderCompleted
{
"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:

  1. Read the raw request body as bytes, before any JSON parsing.
  2. Compute Base64(HMAC-SHA256(key = your secret as UTF-8 bytes, message = raw body)). Use standard Base64 (with +, / and =), not hex.
  3. Compare the result with the X-Webhook-Signature header using a constant-time comparison. Don't include X-Webhook-Timestamp in the calculation.
  4. 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.

Python
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 "")
Node.js
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:

Send a signed test notification
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 200 is ideal. Do your processing after you respond.
  • Don't redirect. Answer at the URL you registered. A redirect (301/302) can turn the POST into a GET without 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.
Build your receiver so that:
  • 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 example consultationId + status + scheduledOnUtc), and always re-read the current state from the API. Don't use X-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.