Skip to main content

Webhook Events

RxScale sends webhooks for the following event types:

HTTP Headers

Every webhook request includes the following HTTP headers: If you configured a custom header when creating the subscription, it will also be included.

pharmacy_order_created

Sent when a new pharmacy order is created and assigned to your pharmacy.
Pharmacy orders are created for over-the-counter (OTC) purchases too, not only for prescription-based orders — any fulfillment routed to your pharmacy triggers this event, whether or not it includes a prescription. For an OTC-only order, data.doctor_data and data.prescription_file are null, and data.prepaid is 0.
data.shipments is always present. On create it is an empty array — pharmacies have not shipped yet. After the pharmacy adds parcels (Pharmacy Portal, or complete_order with tracking_links), later pharmacy_order_updated deliveries include those shipments.
Possible status values: init, waiting for pharmacy, pending review, in-progress, ready_for_pickup, completed

Payload Example

Field Reference

shipping_costs_amount and unpaid_shipping_costs_amount are different fields — the 0-never-null guarantee applies only to the first.data.order.shipping_costs_amount is always an integer: 0 when no shipping is charged, never null.unpaid_shipping_costs_amount is a separate, internal shipping-cost override that is nullable and is not part of the documented webhook contract. If you see it in a payload, do not apply the shipping_costs_amount guarantee to it: it may be absent or null, and null there does not mean “no shipping costs”. Use shipping_costs_amount for shipping reconciliation, and handle unpaid_shipping_costs_amount as an optional, possibly-null value if you read it at all.
Actual payment routing happens only when the pharmacy order is completed. projected payouts in webhook payloads are indicative, only shown for paid physical-prescription orders, and can change before completion. routed payout values are only populated after completion and routing exists.

pharmacy_order_updated

Sent when an existing pharmacy order changes. That includes:
  • Status transitions (for example in-progresscompleted, or a cancellation).
  • The pharmacy adding one or more shipments — from the Pharmacy Portal, or by completing the order through the External Pharmacy API with tracking_links.
The payload structure is identical to pharmacy_order_created. The status field reflects the current status, and data.shipments lists every shipment currently on the order (not only the one just added). There is no separate pharmacy_order_shipment_created event. Subscribe to pharmacy_order_updated and read data.shipments.

Payload Example

Shipments

data.shipments is the list of parcels the pharmacy has recorded on this order. Use it to pick up tracking details when a pharmacy ships — including split shipments (more than one parcel). When the array is filled
  • Completing the order via PATCH /v1/external_pharmacy_api/pharmacy_orders/{uid}/complete_order with tracking_links creates a shipment and then sends pharmacy_order_updated.
  • Adding a shipment in the Pharmacy Portal (manual tracking, or a booked carrier label that is followed by an order update) also leaves the shipment on the order. The next pharmacy_order_updated payload includes all current shipments, ordered by sequence_number.
  • pharmacy_order_created almost always has "shipments": []. Do not treat a missing tracking URL on create as an error.
How to consume it
  • Treat uid as the stable identity of a parcel. A later pharmacy_order_updated may add further entries; existing UIDs stay the same.
  • Prefer tracking_url for customer-facing tracking. tracking_number and carrier_name are present when the pharmacy or carrier supplied them; either can be null on a newly created placeholder shipment.
  • is_test is true only when the parcel was booked against a carrier sandbox (no real dispatch). Ignore test shipments for live fulfillment.
  • return_tracking_number and return_label_expiry (YYYY-MM-DD) are set only after a return label is issued; otherwise they are null.
Internal label-file locations are not included in the webhook. Use tracking_url / tracking_number for tracking, not storage paths.

Organisation-Level Webhooks

When the webhook subscription was created via the Management API (organisation-level), order events include additional data:
  • The sku objects inside order_items and fulfillment.items are enriched with Shopify identifiers (shop_variation_id, shop_product_external_id).
  • A fulfillment object is added with fulfillment order details.
The fulfillment field and the Shopify identifiers (shop_variation_id, shop_product_external_id) inside sku objects are only present in organisation-level webhook deliveries. Pharmacy-level webhooks do not include these fields.

pharmacy_sku_stock_updated

Sent when a pharmacy SKU’s stock level changes. This includes:
  • Direct stock updates via the Pharmacy API (PATCH /v1/.../pharmacy_skus/{uid}/stock or inventory endpoints).
  • Automatic stock reduction when a pharmacy order is completed. This fires for both manual completion (via the pharmacy UI / API) and automatic completion when RxScale detects the order has reached a shipped or completed state via its status-check integration with the pharmacy’s backend. One event is emitted per pharmacy SKU on the completed order.

Payload Example

Field Reference

Organisation-Level Webhooks

When the webhook subscription was created via the Management API (organisation-level), stock events include additional data:
  • The sku object is enriched with Shopify identifiers (shop_variation_id, shop_product_external_id).
  • A shop_identifier field is added at the data level.
The shop_identifier field and the Shopify identifiers (shop_variation_id, shop_product_external_id) inside the sku object are only present in organisation-level webhook deliveries. Pharmacy-level webhooks do not include these fields.

appointment_reminder_due

Sent when a configured appointment reminder becomes due. This event is delivered to organisation-level webhook subscriptions only.
Reminders can also carry direct action links for the patient (join, reschedule, cancel) via RxScale’s own email/SMS content. The rebook_allowed and cancel_allowed fields below tell you whether those actions are currently available — they are only ever true when recipient_role is patient.

Payload Example

Field Reference


patient_doctor_meeting_updated

Sent when a patient-doctor meeting changes lifecycle state. This event is delivered to organisation-level webhook subscriptions only (registered via the Management API). Possible change values:
Use change as the authoritative indicator of what happened. status reflects the meeting’s current database status and is null for on-demand meetings.

Payload Example

Field Reference

Subscribing

Subscribe via the Management API to receive this event:

Email notifications

Separately from webhooks, an organisation can have RxScale email the patient, the doctor, and/or its admins when a meeting is confirmed, cancelled, or rescheduled. Admins configure that from the Notifications tab in the admin portal, and it is off by default. confirmed is subscribable independently of the other two changes — enabling it does not require also enabling cancellation or rescheduling emails. This does not change webhook behaviour. The event still fires on every transition listed above, with the same payload, whether or not any email subscription exists. The webhook payload itself is unchanged by this feature. One difference is worth planning for: a reschedule publishes two events — cancelled for the old meeting and rebooked for the new one — but sends at most one email, the rescheduling one. If you mirror this stream into your own patient messaging, apply the same rule, or the patient hears “cancelled” and then “moved”. confirmed is not part of a reschedule and has no such pairing: it publishes once, for a fresh booking, and sends at most one confirmation email.

Delivery and Idempotency

Webhooks are delivered at least once. Your endpoint should treat deliveries as idempotent using the combination of data.meeting_uid and data.change as the unique key — retried deliveries will carry the same values.