Armada Armada API v2

Webhooks

Webhooks push delivery lifecycle events to your server so you don't have to poll. Configure URLs once per API key; Armada signs the HTTP request, retries on 5xx, and logs every attempt for debugging.

Configuring

Open the API Keys page in the business app, expand a key, and you'll see three webhook blocks:

  • Order update webhookdelivery.* events. Tick which status transitions you want notified on. We recommend all five.
  • Driver location webhook — fires on a configurable interval while the order is en_route. Pick an interval (10s / 30s / 60s / 2m / 5m) — shorter = more server load on your side. Most partners use 30s.
  • Wallet low-balance webhook — fires once when balance drops below your configured threshold. Configure that threshold on the same page.

Headers on every webhook

Field
Type
Description
x-armada-webhook-topic required
string
Event name, e.g. delivery.completed, driver.location_updated.
x-armada-webhook-id required
string
Unique per delivery attempt, prefixed wh_. Use for idempotency: ignore duplicates by id.
x-armada-key-id required
string
The v2 API key that triggered this webhook. Useful when you have multiple keys pointed at one receiver.
x-armada-key-type required
string
Always v2.
x-armada-api-version required
string
Payload schema version. Always v2 today.
x-armada-timestamp required
string (ISO-8601)
When Armada dispatched this webhook.
User-Agent required
string
Always Armada Automated Ordering.

Delivery semantics

  • At-least-once. A network hiccup on your side can produce a duplicate — use x-armada-webhook-id for idempotency.
  • Retries. On a non-2xx response (or a connect error), Armada retries with exponential backoff — 3 attempts over ~15 minutes, then gives up.
  • Order. Events for the same order are sent in order; events across different orders may arrive in any order.
  • Timeout. Armada waits 10 s for a response. Keep your handler fast — do the minimum synchronously (acknowledge, enqueue) and process async.

Minimal receiver

import express from 'express';
const app = express();
app.use(express.json({ limit: '1mb' }));

app.post('/armada-webhook', (req, res) => {
  const topic = req.headers['x-armada-webhook-topic'];
  const id    = req.headers['x-armada-webhook-id'];

  // Idempotency: skip if we've processed this wh_ id already.
  if (seen.has(id)) return res.sendStatus(200);
  seen.add(id);

  switch (topic) {
    case 'delivery.accepted':
    case 'delivery.en_route':
    case 'delivery.completed':
    case 'delivery.failed':
    case 'delivery.canceled':
      onOrderUpdate(req.body);
      break;
    case 'delivery.location_updated':
      onDriverLocation(req.body);
      break;
    case 'wallet.balance_low':
      notifyFinance(req.body);
      break;
  }

  // Return 2xx fast. Any slow work should be queued.
  res.sendStatus(200);
});

Event catalog

delivery.*

Five events, one per lifecycle transition. Payload is the same shape as GET /v2/deliveries/:id at the moment the transition happened.

  • delivery.accepted — driver assigned; driverId becomes non-null.
  • delivery.en_route — driver left the pickup and is moving to the destination.
  • delivery.completed — delivered. Wallet debited.
  • delivery.canceled — pre-terminal cancellation.
  • delivery.failed — dispatcher couldn't complete it.
POST <your-webhook-url> x-armada-webhook-topic: delivery.en_route
{
  "id": "670f2a4e1f6b3c0012abcd12",
  "reference": "order-100245",
  "status": "en_route",
  "code": "A1B2",
  "amount": 4.5,
  "currency": "KWD",
  "deliveryFee": 2,
  "driverId": "580de0389f2e4a3600e4bc7a",
  "driverName": "Ahmad",
  "driverPhone": "+96512345678",
  "driverLocation": { "latitude": 29.3420, "longitude": 47.9560 },
  "customerName": "John Doe",
  "customerPhone": "+96590000000",
  "distance": 4820,
  "duration": 743,
  "trackingLink": "https://tracking.armadadelivery.com/A1B2",
  "testMode": false,
  "createdAt": "2026-04-16T18:12:03.117Z"
}

delivery.location_updated

Fires while the order is en_route, at your configured interval. Minimal payload — use it to update the driver's pin on your tracking UI.

POST <your-webhook-url> x-armada-webhook-topic: delivery.location_updated
{
  "id": "670f2a4e1f6b3c0012abcd12",
  "status": "en_route",
  "driverId": "580de0389f2e4a3600e4bc7a",
  "driverLocation": { "latitude": 29.3421, "longitude": 47.9558 },
  "updatedAt": "2026-04-16T18:14:11.503Z"
}

wallet.balance_low

Fires once when the wallet crosses warningBalanceLevel going down. See Wallet for how the threshold is configured.

POST <your-webhook-url> x-armada-webhook-topic: wallet.balance_low
{
  "balance": 4.25,
  "currency": "KWD",
  "warningBalanceLevel": 5,
  "triggeredAt": "2026-04-16T18:15:01.000Z"
}

Testing your receiver

  1. Expose your local endpoint. ngrok http 3000, cloudflared tunnel --url http://localhost:3000, or a similar tool.
  2. Paste the public URL into the key's webhook field in the business app.
  3. Hit the Send delivery test button (right next to the URL input) — Armada posts a canned payload to your receiver so you can verify wiring without creating an order.
  4. For full lifecycle testing, create an order with a test-mode key. The bot driver plays out all five delivery events in ~30 s.

Webhook logs

Every webhook attempt (success or failure) is logged on the API key. The business-app shows the 100 most recent attempts per key — topic, URL, response status, response time, and the request + response body. Useful for debugging a receiver that's intermittently failing.