Skip to content
Sales Docs

Help · For developers

Webhooks

A webhook sends a POST request to your server the moment an order's status changes in Kushi, so your site or tools can follow along without polling the API. Webhooks are on every plan.

Add an endpoint

You need the "Manage webhooks" permission. Admin/CEO and Media Buyer have it.

  1. Open Settings, then Webhooks.
  2. Choose Add endpoint.
  3. In Endpoint address, enter a full https:// URL on your server.
  4. Under Events, keep Order status changed ticked.
  5. Choose Add endpoint.
  6. Copy the signing secret from Your signing secret. This is the only time it's shown.

If you lose the secret, delete the endpoint and add it again to get a new one.

Events

Event Sent when
order.status_changed An order's status is changed, in the app or with PATCH /public/v1/orders/{id}.
ping You choose Test.

New order (order.created) is listed as Coming soon and isn't sent yet.

What Kushi sends

Each delivery is a POST with Content-Type: application/json and these headers:

Header Value
X-Kushi-Event The event name, e.g. order.status_changed
X-Kushi-Signature sha256= followed by the hex signature

The body:

{
  "event": "order.status_changed",
  "tenantId": "5b1e…",
  "data": {
    "orderId": "a1b2c3d4-…",
    "reference": "A1B2C3D4",
    "externalId": "woo-1042",
    "status": "Delivered",
    "customerName": "Ada Obi",
    "total": 27500
  },
  "timestamp": "2026-10-21T09:14:03.512+00:00"
}

status is the new status, such as Pending, Call Back, Follow Up, Rescheduled, In Transit, Delivered or Rejected. externalId is the reference you sent when creating the order through the API, or null. reference is the short order number shown in the app.

A test delivery has "event": "ping" and "data": { "message": "Test delivery from Kushi Sales" }.

Verify the signature

The signature is an HMAC-SHA256 of the raw request body, using your whole signing secret (including the whsec_ part) as the key, written as lowercase hex.

  1. Read the raw body before parsing it as JSON.
  2. Work out the HMAC-SHA256 of it with your secret.
  3. Compare it with the X-Kushi-Signature value after the sha256= prefix, using a constant-time comparison.
  4. Reject the request if they don't match.

Node.js (Express):

const crypto = require('crypto');

app.post('/kushi-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = crypto.createHmac('sha256', process.env.KUSHI_WEBHOOK_SECRET).update(req.body).digest('hex');
  const got = (req.get('X-Kushi-Signature') || '').replace(/^sha256=/, '');
  const ok = got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
  if (!ok) return res.status(401).end();

  const { event, data } = JSON.parse(req.body);
  // handle event…
  res.sendStatus(200);
});

PHP:

$raw = file_get_contents('php://input');
$got = preg_replace('/^sha256=/', '', $_SERVER['HTTP_X_KUSHI_SIGNATURE'] ?? '');
$expected = hash_hmac('sha256', $raw, getenv('KUSHI_WEBHOOK_SECRET'));
if (!hash_equals($expected, $got)) { http_response_code(401); exit; }

Responses and retries

Reply with any 2xx status to mark the delivery as done. Kushi waits up to 10 seconds for each attempt.

If your server doesn't answer, or answers with anything other than 2xx, Kushi tries twice more, waiting half a second and then one second between attempts, so 3 attempts in all. After that the delivery is recorded as failed and isn't sent again. Your handler should be safe to run twice for the same change.

Test, pause and delete

Each endpoint in the list has three buttons:

  • Test sends a ping straight away. Its result shows under Recent deliveries a moment later.
  • Pause stops deliveries to that endpoint without removing it. The button then reads Switch on. Test is off while an endpoint is paused.
  • Delete removes the endpoint after you confirm. It stops receiving events at once.

Recent deliveries

Recent deliveries lists the latest sends, newest first: Result (Delivered or Failed), Event, Response (your server's HTTP status, or the connection error) and When.

Good to know

  • An endpoint that shows "Every event, including old ones Kushi no longer uses" was added without a list of events. Choose Send order updates only to tidy it.
  • Changing an order to the status it already has can still send an event. Compare with what you stored before acting.
  • Erasing your sales data in Settings clears the delivery history but keeps your endpoints.
  • For a WooCommerce store, the Kushi Sales plugin already verifies and handles these requests. See WooCommerce.