CompuDime API docs

Webhooks

Instead of asking for an order's status again and again, let CompuDime tell you. When an order you sent with POST /v1/orders reaches the store's POS, or a step in the store, CompuDime sends a POST to your receiver.

Register your own receiver

Register a receiver with your order key, a key with the order-intake capability (POST /v1/webhooks). Name the events you want. The answer is 201 with the receiver and its signing secret. The secret is shown in that answer only, never again, not by the API and not in the store owner's console: store it at once and keep it like a password. Managing receivers is free.

curl -s -X POST "https://api.compudime.com/v1/webhooks" \
  -H "Authorization: Bearer $PUBLIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://hooks.example.com/compudime/orders", "events": ["order.dispatched", "order.parked"]}'
{
  "requestId": "register-receiver-1",
  "webhook": {
    "id": "7b0c9a1e-3f52-4c1d-9a7e-2d4f6b8c0e13",
    "url": "https://hooks.example.com/compudime/orders",
    "events": [
      "order.dispatched",
      "order.parked"
    ],
    "active": true,
    "mode": "live",
    "secretPrefix": "whsec_3f9a1c",
    "createdAt": "2026-10-13T15:00:00Z",
    "updatedAt": "2026-10-13T15:00:00Z"
  },
  "secret": "whsec_3f9a1c...",
  "secretNote": "This signing secret is shown once, in this answer, and never again: store it now, like a password. If you lose it, rotate it (POST /v1/webhooks/{webhookId}/rotate-secret)."
}

Send a test event

Before the first order, check that your receiver verifies the signature (POST /v1/webhooks/{webhookId}/test):

curl -s -X POST "https://api.compudime.com/v1/webhooks/$WEBHOOK_ID/test" \
  -H "Authorization: Bearer $PUBLIC_API_KEY"

The answer is 202 with the delivery. Within about a minute CompuDime posts a webhook.test event to the receiver, with the same headers and signature as an order event and the same retries. It is about no order: check the signature, answer 2xx, and do nothing else with it.

{
  "id": "c2e8f4a0-6b1d-4e3f-8a9c-0d2e4f6a8b1c",
  "type": "webhook.test",
  "createdAt": "2026-10-13T15:01:00Z",
  "data": {
    "webhookId": "7b0c9a1e-3f52-4c1d-9a7e-2d4f6b8c0e13",
    "mode": "live",
    "requestedAt": "2026-10-13T15:01:00Z",
    "requestId": "test-receiver-1",
    "note": "A test event you asked for with POST /v1/webhooks/{webhookId}/test. It is not about an order: check the signature, answer 2xx, and do nothing else with it."
  }
}

See how it went with GET /v1/webhooks/{webhookId}/deliveries: each delivery's state (pending, failed, delivered or dead), its attempts, and the status and error of the last one. You may send 10 test events to a receiver in an hour; more is 429 rate_limited with Retry-After. A receiver the store owner turned off is 409 webhook_inactive.

List, rotate and remove

Test keys: a receiver you register with a test key gets only the test events you send it, because a test key's orders never send a webhook. A test key never sees a live key's receivers, and a live key never sees a test key's. Receivers CompuDime staff registered for you are live receivers.

CompuDime staff can still register a receiver for you: write to support@compudime.com with the store, the URL and the events.

Events

EventWhen
order.dispatchedThe order landed in the store's POS: data.invoice links the POS invoice (by feed identity), and data.posOrderRef is the store's own reference, not an invoice number. Treat it as final for dispatch; the fulfillment events (order.picked and the ones after it) may follow.
order.parkedDispatch stopped (a permanent POS rejection, the retry bound, or the store's operator parked the order): data.dispatchError says why. The owner can retry it from the console, after which order.dispatched may follow.
order.pickedThe store finished picking the order: data.fulfillment.pickedAt. Sent once per order and receiver, when the order status sync first sees the step (it reads the store every few minutes, so a step can be announced minutes after it happened, and two steps can arrive together or out of order: go by data.fulfillment.state). data.fulfillment is the whole fulfillment object as the order reads show it; data.invoice links the POS invoice. Only for orders at stores whose agent reads order status for the platform.
order.out_for_deliveryA driver took the order: data.fulfillment.outForDeliveryAt, and data.fulfillment.eta when the store recorded one. Sent once per order and receiver, when the order status sync first sees the step (it reads the store every few minutes, so a step can be announced minutes after it happened, and two steps can arrive together or out of order: go by data.fulfillment.state). data.fulfillment is the whole fulfillment object as the order reads show it; data.invoice links the POS invoice. Only for orders at stores whose agent reads order status for the platform.
order.deliveredThe store recorded the delivery as done: data.fulfillment.deliveredAt. Final. Sent once per order and receiver, when the order status sync first sees the step (it reads the store every few minutes, so a step can be announced minutes after it happened, and two steps can arrive together or out of order: go by data.fulfillment.state). data.fulfillment is the whole fulfillment object as the order reads show it; data.invoice links the POS invoice. Only for orders at stores whose agent reads order status for the platform.
order.completedAn order that is not a delivery was rung up at the store's register (the customer checked out): data.fulfillment.completedAt. Final. Sent once per order and receiver, when the order status sync first sees the step (it reads the store every few minutes, so a step can be announced minutes after it happened, and two steps can arrive together or out of order: go by data.fulfillment.state). data.fulfillment is the whole fulfillment object as the order reads show it; data.invoice links the POS invoice. Only for orders at stores whose agent reads order status for the platform.
order.canceledThe store deleted the order from its POS, which is how the POS cancels an order: data.fulfillment.canceledAt. Final. Sent once per order and receiver, when the order status sync first sees the step (it reads the store every few minutes, so a step can be announced minutes after it happened, and two steps can arrive together or out of order: go by data.fulfillment.state). data.fulfillment is the whole fulfillment object as the order reads show it; data.invoice links the POS invoice. Only for orders at stores whose agent reads order status for the platform.
webhook.testSent only when you ask: POST /v1/webhooks/{webhookId}/test. It is about no order; use it to check that your receiver verifies the signature and answers 2xx.

Each event is sent at most once per order and receiver. Steps in the store are seen every few minutes, so two can arrive together or out of order: go by data.fulfillment.state. order.parked is not always the last word: the store can retry a parked order, and you then get order.dispatched.

An order sent with a test key is simulated and never reaches the store, so it sends no webhooks. See Test orders.

The body

Every delivery has the same shape: id, type, createdAt and data. The full shape is OrderWebhookEvent.

{
  "id": "5d2f0f0c-8a1c-4d5e-9b52-1c4a6d0f7e21",
  "type": "order.dispatched",
  "createdAt": "2026-09-16T12:00:07Z",
  "data": {
    "orderId": "1a2b3c4d-0000-4000-8000-000000000001",
    "externalOrderId": "web-1001",
    "status": "accepted",
    "dispatchState": "dispatched",
    "posOrderRef": "OrderExchange CDAPI:int_123#13697638",
    "dispatchError": "",
    "attempts": 0,
    "invoice": {
      "externalId": "web-1001",
      "processorCode": "CDAPI:int_123",
      "url": "https://api.compudime.com/v1/invoices/external/web-1001?processorCode=CDAPI%3Aint_123"
    }
  }
}

Headers

NameTypeDescription
X-CompuDime-Event requiredstringThe event name, the same as type in the body.
X-CompuDime-Delivery requiredstringThe delivery id, the same as id in the body.
X-CompuDime-Timestamp requiredstringWhen this attempt was sent (RFC 3339, UTC).
X-CompuDime-Signature requiredstringsha256= followed by the hex HMAC-SHA256 of the raw request body under the receiver's signing secret.

Check the signature

CompuDime POSTs this body to each active live receiver registered for your order-intake integration (by you through POST /v1/webhooks, or by CompuDime) and subscribed to order.dispatched. Verify X-CompuDime-Signature (sha256=<hex HMAC-SHA256 of the raw body under the receiver's secret>) before trusting it, and deduplicate on id: retries and redeliveries carry the same id. Deliveries are not billed. A test key's orders are simulated and never send a webhook.

Compute HMAC-SHA256 over the raw bytes you received (not over a parsed and re-written object), with your signing secret as the key. Write it in hex, put sha256= in front, and compare it with X-CompuDime-Signature in constant time.

// Node, Express with a raw body
import crypto from "node:crypto";
app.post("/compudime/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto.createHmac("sha256", process.env.COMPUDIME_WEBHOOK_SECRET).update(req.body).digest("hex");
  const given = req.get("X-CompuDime-Signature") ?? "";
  if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
    return res.status(401).end();
  }
  const event = JSON.parse(req.body);
  // skip event.id if you handled it before, then act on event.type
  res.status(204).end();
});
# Python
import hashlib, hmac
def verify(secret: str, raw_body: bytes, header: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(header.strip(), expected)

Answer, and retries

Any 2xx within 10 seconds acknowledges the delivery; the body is ignored. Anything else, a timeout or a redirect is a failed attempt and is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours.

Redirects are never followed. Retries carry the same id, so record the ids you handled and answer 2xx again without doing the work twice. Deliveries are not billed.