Webhooks
CardNexus sends webhook events to your registered endpoints when something happens on your account that's worth knowing about — an order comes in, your balance updates, an inventory import finishes, or a buyer messages you.
How delivery works
Webhooks are delivered through Svix, a managed delivery service. Svix handles signing, retries with exponential backoff, replay of failed deliveries, and an embedded portal where you manage endpoints and inspect delivery logs.
- Open Settings → API → Webhooks at cardnexus.com.
- Add an endpoint (your HTTPS URL).
- Pick the event types you want.
- CardNexus signs every delivery; you verify the signature server-side before trusting the payload.
Signature verification
Every delivery carries three headers:
| Header | Meaning |
|---|---|
webhook-id |
The delivery id, unique per delivery. |
webhook-timestamp |
Epoch seconds at send time. Reject deliveries older than ~5 minutes to mitigate replay. |
webhook-signature |
HMAC-SHA256 of id.timestamp.body keyed with your endpoint's signing secret. |
Svix publishes verified libraries for Node.js, Python, Go, Ruby, PHP, Rust, Java, C#, and Elixir — use them rather than rolling your own HMAC. Their docs cover each runtime: docs.svix.com.
Event envelope
Every webhook payload follows the same shape:
{
"type": "order.created",
"eventId": "OR-ASNBC-1.seller",
"timestamp": "2024-08-14T10:23:11.000Z",
"account": "65f3a2b1c8d4e9f7a6b5c4d3",
"data": {
"order": { "orderNumber": "OR-ASNBC-1" }
}
}
| Field | Meaning |
|---|---|
type |
The dotted event name (see Event types). |
eventId |
Stable across retries of the same delivery. Safe to dedupe on. |
timestamp |
When the event fired (UTC, ISO 8601). |
account |
The account the event belongs to. For your own webhooks this is your account id. |
data |
The per-event payload. Its shape is documented per event type in the API reference. |
Order events also carry a recipientRole field (seller or buyer) so you can tell which side of the order the event is about.
Event types
| Event | Fires when |
|---|---|
offer.created |
An opening offer is sent. Both participants receive a copy. |
offer.updated |
An offer is countered, accepted, declined, cancelled, or voided. Both participants receive a copy; integrations require offers:read. See Offers. |
order.created |
A new order is placed. |
order.status.changed |
An order moves to a new status. |
message.received |
You receive a message. |
account.updated |
A setting on your account changes: whether CardNexus manages shipping on your new sales. Integrations require account:read. |
balance.updated |
Your wallet balance changes. |
inventory.quantity.changed |
An inventory line's quantity changes. |
inventory.import.completed |
A bulk inventory import finishes. |
inventory.export.completed |
A bulk inventory export finishes. |
optimizer.run.completed |
A cart-optimizer run finishes. |
optimizer.run.failed |
A cart-optimizer run fails. |
Order events are delivered to both sides of the order, with recipientRole naming which one a delivery is for. On order.status.changed, the seller's copy carries the sale's data.metadata — your own key/value pairs — so a delivery can be matched to your record without a follow-up call; the buyer's copy has data.metadata as null. See Track sales in your own system.
Both order events carry data.shippingManagedByCardNexus. It is true when CardNexus manages shipping on the order — the buyer paid CardNexus for the shipping and the seller ships with a label generated on CardNexus — and false when the seller ships the order themselves. It is set when the order is placed and never changes afterwards: when a seller's shipping setting changes, only orders placed after the change follow the new setting. The same value is on shippingManagedByCardNexus for GET /v1/sales/{orderNumber} and GET /v1/purchases/{orderNumber}.
order.status.changed also carries data.shippingService, the shipping the buyer paid for: tracked or untracked. An untracked order ships as a letter, which may have no tracking number. data.untrackedCloseAt is the date an untracked order closes on its own once shipped; it is null on tracked orders and before an untracked order ships. The same values are on shippingService and untrackedCloseAt for GET /v1/sales/{orderNumber} and GET /v1/purchases/{orderNumber}.
Your current setting for new sales is seller.shippingManagedByCardNexus on GET /v1/account/me. When it changes, you receive account.updated, with data.changedFields set to ["seller.shippingManagedByCardNexus"] and the new value in data.seller.shippingManagedByCardNexus.
Idempotency
Deliveries are at-least-once and can arrive out of order. Dedupe on eventId, and process each event only once.
Acknowledging
Return 2xx to acknowledge a delivery. Anything else is treated as a failure and retried by Svix with exponential backoff. Don't return 4xx for "I don't recognise this event type" — log it and return 2xx instead, otherwise the delivery keeps retrying.
Building an integration?
If you're building an application that acts on other users' accounts, you receive every connected account's events at one endpoint, with the account field naming the account each event belongs to. See the Integrations guide for the fan-out model and how to set up your endpoint.