Skip to content

Webhooks ​

Webhooks let Wasal push order events to your server in real time, so you don't need to poll.

How it works ​

  1. Register an HTTPS endpoint URL.
  2. When a matching event occurs, Wasal sends a POST request to your URL with a signed JSON payload.
  3. Respond with any 2xx status within 8 seconds to acknowledge delivery.

Delivery is attempted once. Each event is sent a single time with an 8-second timeout — there is no automatic retry queue. If your endpoint is down, reconcile missed events by polling GET /order or the order's status history.


Payload format ​

Every webhook request has this shape:

json
{
  "event": "order.status_changed",
  "timestamp": "2025-06-01T10:30:00.000Z",
  "data": { /* event-specific payload */ }
}

Headers sent on every request:

HeaderValue
Content-Typeapplication/json
X-Wasal-EventEvent name (e.g. order.status_changed)
X-Wasal-Signaturesha256=<hmac-hex>

Verifying the signature ​

Compute HMAC-SHA256 of the raw request body using your webhook's secret, then compare with the X-Wasal-Signature header.

js
const crypto = require('crypto');

function isValidSignature(rawBody, secret, signatureHeader) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected)
  );
}

Always use timingSafeEqual to prevent timing attacks. Reject any request where the signature doesn't match.


Available events ​

EventTriggered when
order.createdA new order is placed.
order.status_changedAn order's status changes (e.g. pending → assigned).
order.deliveredAn order is delivered.
order.cancelledAn order is cancelled.

If you register a webhook without specifying events, all four events are subscribed by default.

Event data fields:

Eventdata payload
order.created{ orderId, orderNumber, status }
order.status_changed{ orderId, orderNumber, status, previousStatus }
order.delivered{ orderId, orderNumber, status, previousStatus? }
order.cancelled{ orderId, orderNumber }

Order statuses ​

The status and previousStatus fields use the following values. See the order lifecycle for their meanings and normal transition sequence.

StatusSent by
draftorder.created when an order is created for later activation.
pendingorder.created or order.status_changed.
assignedorder.created when immediately auto-assigned, or order.status_changed.
on_way_to_merchantorder.status_changed.
picked_uporder.status_changed.
in_transitorder.status_changed.
on_holdorder.status_changed.
deliveredorder.delivered.
failedorder.status_changed.
returnedorder.status_changed.
cancelledorder.status_changed for driver-led or demo transitions. Merchant-initiated cancellation sends order.cancelled; its event name implies the resulting status.

Treat previousStatus as optional. It is included for order.status_changed and may be included for order.delivered; consumers should not require it for terminal events.


Register a webhook ​

POST /integration/merchant/webhook ​

json
{
  "url": "https://yourstore.com/api/wasal-webhook",
  "events": ["order.created", "order.status_changed"]
}
FieldRequiredNotes
urlYesMust start with https://.
eventsNoArray of event names. Omit to subscribe to all events.

Response: 201 Created

json
{
  "success": true,
  "data": {
    "webhook": {
      "_id": "665f...",
      "url": "https://yourstore.com/api/wasal-webhook",
      "events": ["order.created", "order.status_changed"],
      "active": true,
      "secret": "a3f8b2...",
      "createdAt": "2025-06-01T10:00:00.000Z"
    }
  }
}

Save the secret. It is shown only once at creation. You will need it to verify incoming requests.


List webhooks ​

GET /integration/merchant/webhook ​

Returns all registered webhooks for your merchant. The secret is not included in list or get responses.

Response: 200 OK — not paginated (merchants typically register only a handful of webhooks).

json
{
  "success": true,
  "data": {
    "list": [
      {
        "_id": "665f...",
        "url": "https://yourstore.com/api/wasal-webhook",
        "events": ["order.created", "order.status_changed"],
        "active": true,
        "createdAt": "2026-06-01T09:00:00.000Z"
      }
    ]
  }
}

Update a webhook ​

PUT /integration/merchant/webhook/:webhookId ​

Updates the URL, events, or active state. Only send the fields you want to change.

json
{
  "url": "https://yourstore.com/api/wasal-v2",
  "events": ["order.delivered"],
  "active": false
}

Response: 200 OK — updated webhook object (without secret).


Delete a webhook ​

DELETE /integration/merchant/webhook/:webhookId ​

Removes the webhook. Deliveries in flight will still complete.


Send a test ping ​

POST /integration/merchant/webhook/:webhookId/test ​

Sends a test ping event to the registered URL. Useful to verify your endpoint is reachable and your signature verification logic is correct.

Test payload delivered to your URL:

json
{
  "event": "ping",
  "webhookId": "665f1e2a9b3c4d5e6f708192",
  "timestamp": "2025-06-01T10:30:00.000Z",
  "data": { "message": "This is a test ping from Wasal" }
}

Returns 200 if your endpoint responded with a 2xx, or 502 WEBHOOK_UNREACHABLE if it was unreachable.


Errors ​

HTTPcodeMeaning
400VALIDATION_FAILEDurl missing or not https://, or invalid event names — see errors.
404WEBHOOK_NOT_FOUNDWebhook does not exist or is not owned by your merchant.
502WEBHOOK_UNREACHABLETest ping could not be delivered to the webhook URL.

Wasal Delivery Platform · Integration API v1.0.0