Concept Developers

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_allowed in 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_feature in the API and the plan message in the panel); the API route also needs the api flag. 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 or 0 leaves 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#

  1. 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".

  2. 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".

  3. 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.

  4. Try it with "Send test"

    The platform sends a signed ping message to your URL and shows your system's HTTP status or the reason for the failure.

  5. Or register through the API (merchants)

    Send POST /api/v1/webhooks with your API key. The 201 response contains the secret field once only.

  1. Your current API keys (shown masked)
  2. The name of the platform or system you connect
  3. Generate a new key
  4. Open the API documentation
API keys and connecting your system (demo data)
bash
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_changed and the stage event (such as parcel.delivered), each with its own X-DS-Event-Id. A new parcel normally sends parcel.created and then parcel.status_changed (status 5); an awaiting_funds parcel funded later sends parcel.status_changed, not parcel.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 sends parcel.status_changed. Read the parcel with GET /parcels/{tracking} when you need its current state.
  • wallet.* events are for merchants only. A company receives the eight parcel.* events for its own parcels, and the message body adds merchant and deliveryman.

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
json
{
  "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 data is the same parcel object as in the API (shortened in the example). wallet.* events carry id, amount, currency (always USD) and recharge_balance, plus fields that depend on the event.
  • status_label (in the full parcel object) is localized text, so rely on status_code and stage. created_at is in UTC (+00:00).

Common mistakes#

  • A slow or redirecting system (such as www.example.com to example.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/events
  • GET /api/v1/webhooks
  • POST /api/v1/webhooks
  • PATCH /api/v1/webhooks/{id}
  • DELETE /api/v1/webhooks/{id}
  • POST /api/v1/webhooks/{id}/test