Skip to main content

Webhooks

A webhook endpoint is a URL you register for an organization. When a subscribed event happens, the worker POSTs a signed JSON body to it. Endpoints are managed under Settings → Webhooks or via POST /admin/webhooks (see API reference).

Registering an endpoint

Give an Endpoint URL (HTTPS), an optional description, and the events you want — leave the list empty to receive everything. Creating the endpoint reveals its signing secret once; copy it. Rotate signing secret issues a new one and stops the old immediately.

The delivery

POST /your/endpoint HTTP/1.1
Content-Type: application/json
X-Webhook-Event: order.placed
X-Webhook-Signature: 3f9c…e1 (hex HMAC-SHA256 of the raw body, keyed with the endpoint secret)

{
"event": "order.placed",
"data": { …event payload… },
"sent_at": "2026-09-05T09:12:44Z"
}

X-Sokisoko-Event and X-Sokisoko-Signature are sent as well for older consumers; they are deprecated in favour of the neutral names. The request times out after 15 seconds.

Respond with any 2xx quickly and do your work afterwards. A non-2xx response or a timeout marks the attempt failed and the job queue retries with backoff; every attempt is recorded with its attempt number, response status and error, and shows in the endpoint's Delivery log in the admin console. Replay (POST /admin/webhooks/deliveries/{id}/replay) re-queues a delivery on demand — useful after you fix a bug on your side.

Deliveries are at-least-once. Make your handler idempotent on the event's identifying fields (for example the order public_id plus the new status).

Verifying the signature

Compute HMAC-SHA256 over the raw request body bytes with your endpoint secret, hex-encode it, and compare in constant time with X-Webhook-Signature.

import { createHmac, timingSafeEqual } from 'node:crypto'

export function verify(rawBody, header, secret) {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
return expected.length === header.length &&
timingSafeEqual(Buffer.from(expected), Buffer.from(header))
}
import hmac, hashlib

def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)

Parse the JSON only after the signature checks out, and read the body as bytes before any framework re-serialises it.

Event catalogue

EventWhen
rfq.submittedA buyer submitted a request for quote
sourcing.quotedA supplier store on the platform sent a quote for one of your sourcing requests
quote.sentA quote (or a new version) was sent to the buyer
quote.counteredThe buyer proposed changes
quote.accepted / quote.declinedThe buyer decided
order.placedAn order was created — checkout, quote acceptance, or a rep placing it on behalf of a customer
order.status_changedAny order transition (confirmed, processing, shipped, delivered, closed, cancelled, on hold)
order.backorderedA confirmed order could not fully reserve stock
approval.requestedAn order went on hold for the buyer company's approver
order.approval_decidedThe approver released or rejected it
shipment.status_changedA shipment moved (pending, shipped, delivered, returned)
invoice.paidAn invoice was settled in full
invoice.undunnableThe dunning sweep found an overdue invoice whose customer has no contact email
payment.refundedA payment was refunded
order.refund_dueA cancellation left money to return
return.requested / return.decidedA return was requested; approved, received or rejected
lead.createdA storefront enquiry, popup submission or manual lead
record_message.postedA message on an order, quote or invoice thread
product.submittedA vendor listing entered moderation
vendor.application_submittedSomeone applied to sell
vendor_order.status_changed / vendor_order.cancelledA marketplace sub-order moved or was cancelled
subscription.run_failedA recurring order could not be placed

Payloads carry the entity's public_id and the fields relevant to the event; fetch the full record from the API when you need more. The same events drive the built-in automation rules, so anything you can automate in the console you can also react to externally.