Outgoing webhooks: instant notifications to your system
What outgoing webhooks are: the ten events plus ping, message format and headers, registering a URL in the panel or API, URL rules (https, ports 443 and 8443, public address), plan flag and limits.
Last updated 22 October 2026 · Last verified 22 October 2026
What outgoing webhooks are#
Instead of your system asking the platform every minute "did anything change?", the platform sends a POST request with a JSON body to an HTTPS URL you choose as soon as an event happens, such as a parcel being delivered. Every message is signed and retried on failure (see the signature verification and retries articles).
Who can use them#
- Merchants (platform merchants): from the merchant panel or the API. A merchant created by a delivery company cannot (
403 api_not_allowedin the API, a warning redirect in the panel). - Delivery company admins: from the company panel only (the company API has no webhook endpoints). A company receives events for its own parcels only.
- Plan (when plans apply to your account): the "Webhook" plan flag must be on to add a new link (otherwise
403 plan_featurein the API and the plan message in the panel); the API route also needs theapiflag. The number of links is the lower of 10 and the plan's "Number of webhook URLs" (new plan forms default to 3 for merchants and 5 for companies; an empty value or0leaves only the ceiling of 10).
URL requirements#
- It starts with
https://only and is at most 500 characters, because messages carry customer data. - No username or password inside the URL (
user:pass@), and no port other than 443 or 8443. - The domain must resolve through DNS and every address (IPv4 and IPv6) must be public: private, reserved, loopback, link-local and CGNAT (
100.64.0.0/10) addresses are rejected. The check is repeated on every delivery. - Redirects are not followed (a 3xx counts as a failure), and your system must answer 2xx within 10 seconds.
Register a URL#
Open the webhook page
Merchant: from "Webhook and API" open the card "Notifications to your system (outgoing Webhook)" and click "Manage webhooks". Company admin: from "Integrations and API" click "Webhook" or "Manage links and log".
Add a link
Open "New link", enter the "Receiving URL" (optionally a "Name (optional)"), tick the events you want or "All events" ("Parcel status changed" is ticked by default), keep "Enabled" ticked and click "Add".
Copy the signing key
After adding, the "Signing key" is shown in full; afterwards it is masked, with icons to show, copy and renew it. Renewing invalidates the old key immediately.
Try it with "Send test"
The platform sends a signed
pingmessage to your URL and shows your system's HTTP status or the reason for the failure.Or register through the API (merchants)
Send
POST /api/v1/webhookswith your API key. The201response contains thesecretfield once only.
- Your current API keys (shown masked)
- The name of the platform or system you connect
- Generate a new key
- Open the API documentation
curl -X POST https://YOUR_PLATFORM_DOMAIN/api/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Warehouse system","url":"https://example.com/webhooks/delivery-syria","events":["parcel.delivered","parcel.returned"]}'
The API also has GET /api/v1/webhooks, PATCH and DELETE on /api/v1/webhooks/{id}, POST /api/v1/webhooks/{id}/test and GET /api/v1/webhooks/events. Choose * or exact event names (parcel.* gives 422 validation_failed). The message log and resend exist in the panel only.
Events#
Ten events, plus ping:
| Event | Sent when |
|---|---|
parcel.created |
A parcel is created (dashboard, API or a connected store) |
parcel.status_changed |
Status changes made through the parcel workflow (delivery, returns, back to the warehouse, the generic status update and external-carrier sync) and the automatic move to status 5 (received at the warehouse) right after creation; not sent by the dedicated staff pickup and hub forms or by some of the undo forms (see below) |
parcel.out_for_delivery |
The parcel is with the courier (statuses 7 and 8) |
parcel.delivered |
Fully or partially delivered (9 and 32); includes cod_collected |
parcel.returning |
Delivery failed and the parcel is on its way back |
parcel.returned |
The merchant received the returned parcel (30 and 13) |
parcel.cancelled |
The connected external carrier reported the parcel as cancelled and the amount went back to your balance (not sent if the parcel is already delivered, fully or partially, received back by the merchant or at the failed-delivery return step, or was already refunded). Not sent when you cancel with POST /parcels/{tracking}/cancel |
parcel.awaiting_funds |
The order was saved because the balance is too low (with wait_for_balance or store orders) |
wallet.topup |
Balance was added to the recharge wallet (manual approval or online payment) |
wallet.withdrawal |
A payout withdrawal request was approved or rejected |
ping |
The test message; it cannot be selected as an event |
- One status change can send two messages:
parcel.status_changedand the stage event (such asparcel.delivered), each with its ownX-DS-Event-Id. A new parcel normally sendsparcel.createdand thenparcel.status_changed(status 5); anawaiting_fundsparcel funded later sendsparcel.status_changed, notparcel.created. - The dedicated staff pickup and hub forms (assign or reschedule a pickup, received by the pickup man, transfer to a hub, received at a hub) and the undo forms of the pickup, warehouse, hub and delivery-assignment steps (undo of pickup assignment, pickup reschedule, received by the pickup man, received at the warehouse, transfer to a hub, received at a hub, delivery assignment and delivery reschedule) send no webhook. Undoing a delivery, a partial delivery or a return step does send
parcel.status_changed. A status set through the generic status-update path (staff mobile API, the generic status route, external-carrier sync) also sendsparcel.status_changed. Read the parcel withGET /parcels/{tracking}when you need its current state. wallet.*events are for merchants only. A company receives the eightparcel.*events for its own parcels, and the message body addsmerchantanddeliveryman.
What a message looks like#
A POST with Content-Type: application/json and User-Agent: DeliverySyria-Webhook/2.0, carrying these headers:
| Header | Meaning |
|---|---|
X-DS-Event |
The event name |
X-DS-Event-Id |
The event id (evt_ plus 24 lowercase characters); the same for all attempts and links |
X-DS-Delivery |
A UUID for each message per link; the same across attempts |
X-DS-Attempt |
The attempt number, starting at 1 |
X-DS-Timestamp |
Send time in Unix seconds, renewed on every attempt |
X-DS-Signature |
sha256= followed by the hex signature |
{
"id": "evt_xxxxxxxxxxxxxxxxxxxxxxxx",
"event": "parcel.delivered",
"created_at": "2026-10-14T14:32:05+00:00",
"data": {
"id": 1043,
"tracking_number": "DS261013AB12CD",
"reference": "ORDER-1043",
"stage": "delivered",
"status_code": 9,
"recipient": { "name": "Example Name", "phone": "0900000000" },
"cod_amount": 50.0,
"cod_collected": 50.0,
"tracking_url": "https://YOUR_PLATFORM_DOMAIN/t/DS261013AB12CD"
}
}
- For parcel events
datais the same parcel object as in the API (shortened in the example).wallet.*events carryid,amount,currency(alwaysUSD) andrecharge_balance, plus fields that depend on the event. status_label(in the full parcel object) is localized text, so rely onstatus_codeandstage.created_atis in UTC (+00:00).
Common mistakes#
- A slow or redirecting system (such as
www.example.comtoexample.com): counted as a failure. Answer 2xx at once and process in the background. - Assuming a message arrives once and in order: it can repeat or arrive late; ignore what you already handled using
X-DS-Event-Id. - A suspended link receives nothing: events are not stored while it is suspended (see the retries article).
Permissions and limits#
- In the panel, adding a link, testing and resending are limited to 20, 10 and 30 per minute respectively (the API limits are in the rate limits article).
- If the plan lacks the Webhook flag the platform sends no events; an expired subscription alone does not stop sending.
API
GET /api/v1/webhooks/eventsGET /api/v1/webhooksPOST /api/v1/webhooksPATCH /api/v1/webhooks/{id}DELETE /api/v1/webhooks/{id}POST /api/v1/webhooks/{id}/test