Developers

Blink Business API

Quote, book and track deliveries from your store or system. Sandbox, scoped keys, idempotency and signed webhooks.

Download openapi.yamlGet an API key

Delivery & retries

Blink POSTs { event, data, at } as JSON to every active endpoint whose event filter and mode match. Reply 2xx within 8 s. Otherwise the delivery retries with exponential backoff (30 s, 1 min, 2 min, 4 min … capped at 1 day) and is dead-lettered after the endpoint's maxAttempts (1–20, default 8). Every attempt is logged with request headers, response status, body and latency; replay any delivery (new delivery id, same payload) from the portal or POST /v1/business/webhooks/deliveries/{id}/replay.

Deliveries can repeat or arrive out of order. Sandbox orders carry data.sandbox: true and only go to endpoints in test or all mode.

Payload
{
  "event": "order.delivered",
  "data": {
    "code": "BL3F9A21C0DE",
    "externalRef": "WC-10421",
    "status": "delivered",
    "driverId": "0b6c…",
    "trackUrl": "https://blink.example.my/t/9f2c…"
  },
  "at": "2026-10-26T08:15:30.000Z"
}

Headers

HeaderValue
X-Blink-Signaturev1: hex HMAC-SHA256(raw body, endpoint secret). Unchanged from v1, always sent.
X-Blink-Signature-V2t=<unix seconds>,v1=<hex HMAC-SHA256("<t>.<raw body>", secret)>. Prefer this; reject timestamps older than 5 min.
X-Blink-Signature-Version2
X-Blink-EventThe event name, e.g. order.delivered.
X-Blink-DeliveryDelivery id. Replays get a new id; dedupe on data.code + event.
X-Blink-Modelive or test (sandbox orders).

Verify the signature

Use the raw request body; re-serialised JSON will not match. Compare in constant time.

Node
import { createHmac, timingSafeEqual } from "node:crypto";

// v2: X-Blink-Signature-V2: t=<unix seconds>,v1=<hex HMAC-SHA256("<t>.<raw body>", secret)>
export function verifyBlink(rawBody: string, header: string, secret: string, toleranceS = 300) {
  const m = /t=(\d+),v1=([0-9a-f]+)/.exec(header ?? "");
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > toleranceS) return false;
  const expected = createHmac("sha256", secret).update(`${m[1]}.${rawBody}`).digest("hex");
  return expected.length === m[2].length && timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]));
}

// express: app.post("/blink/webhook", express.raw({ type: "application/json" }), (req, res) => {
//   if (!verifyBlink(req.body.toString("utf8"), req.header("x-blink-signature-v2"), process.env.BLINK_WEBHOOK_SECRET)) return res.sendStatus(401);
//   const { event, data } = JSON.parse(req.body); res.sendStatus(200);
// });

Events

order.searching

Order booked (or payment received) and looking for a driver.

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "status": "searching",
  "driverId": null,
  "trackUrl": "https://blink.example.my/t/9f2c…"
}
order.assigned

A driver accepted the job.

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "status": "assigned",
  "driverId": "0b6c…",
  "trackUrl": "https://blink.example.my/t/9f2c…"
}
order.at_pickup

Driver arrived at the (first) pickup.

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "status": "at_pickup",
  "driverId": "0b6c…",
  "trackUrl": "https://blink.example.my/t/9f2c…"
}
order.picked_up

Items collected.

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "status": "picked_up",
  "driverId": "0b6c…",
  "trackUrl": "https://blink.example.my/t/9f2c…"
}
order.in_transit

On the way to the drop-offs.

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "status": "in_transit",
  "driverId": "0b6c…",
  "trackUrl": "https://blink.example.my/t/9f2c…"
}
order.delivered

All stops done; proof of delivery recorded.

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "status": "delivered",
  "driverId": "0b6c…",
  "trackUrl": "https://blink.example.my/t/9f2c…"
}
order.cancelled

Cancelled by you, ops or no driver found.

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "status": "cancelled",
  "driverId": "0b6c…",
  "trackUrl": "https://blink.example.my/t/9f2c…"
}
order.failed

Delivery could not be completed.

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "status": "failed",
  "driverId": "0b6c…",
  "trackUrl": "https://blink.example.my/t/9f2c…"
}
stop.arrived

Driver arrived at one stop of a multi-stop order.

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "trackUrl": "https://blink.example.my/t/9f2c…",
  "stop": {
    "seq": 2,
    "kind": "dropoff",
    "ref": "Aisyah",
    "address": "12 Jalan SS2/24, Petaling Jaya"
  }
}
stop.completed

A stop was completed (items handed over or collected).

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "stop": {
    "seq": 2,
    "kind": "dropoff",
    "ref": "Aisyah",
    "address": "12 Jalan SS2/24, Petaling Jaya"
  },
  "failReason": null,
  "podRecipient": "Aisyah",
  "items": [
    {
      "name": "Cake box",
      "qty": 2
    }
  ]
}
stop.failed

A stop could not be completed; undelivered items return.

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "stop": {
    "seq": 2,
    "kind": "dropoff",
    "ref": "Aisyah",
    "address": "12 Jalan SS2/24, Petaling Jaya"
  },
  "failReason": "Recipient not reachable",
  "podRecipient": null
}
stop.cancelled

A stop was cancelled.

data
{
  "code": "BL3F9A21C0DE",
  "externalRef": "WC-10421",
  "stop": {
    "seq": 2,
    "kind": "dropoff",
    "ref": "Aisyah",
    "address": "12 Jalan SS2/24, Petaling Jaya"
  },
  "reason": "Customer cancelled"
}
recurring.booked

A recurring run booked its order (scheduledAt set when booked ahead).

data
{
  "recurringId": "7c0e…",
  "name": "Morning bakery run",
  "slot": "202610270700",
  "code": "BL9A…",
  "externalRef": "REC-7c0e…-202610270700",
  "trackUrl": "https://blink.example.my/t/…",
  "scheduledAt": "2026-10-26T23:00:00.000Z",
  "nextRunAt": "2026-10-27T23:00:00.000Z"
}
recurring.failed

A recurring run could not book (e.g. credit limit).

data
{
  "recurringId": "7c0e…",
  "name": "Morning bakery run",
  "slot": "202610270700",
  "externalRef": "REC-7c0e…-202610270700",
  "error": "Credit limit exceeded",
  "nextRunAt": "2026-10-27T23:00:00.000Z"
}
widget.requested

A customer asked for a delivery through your booking widget.

data
{
  "requestId": "e41b…",
  "requester": {
    "name": "Aina",
    "phone": "0193344556"
  },
  "to": {
    "address": "KLCC",
    "lat": 3.1579,
    "lng": 101.7116
  },
  "quoteSen": 1250,
  "requireApproval": true
}
test.ping

"Send test event" in the portal or POST /v1/business/webhooks/{id}/test.

data
{
  "message": "Hello from Blink",
  "endpointId": "a1f2…"
}