Sellvik / developers
Webhooks

Event catalogue

Every event Sellvik can deliver, with payload shapes.

Envelope

Every webhook delivery uses the same envelope:

{
  "id": "evt_a1b2c3d4e5f6...",
  "type": "order.created",
  "createdAt": "2026-05-27T13:45:00.000Z",
  "shopSubdomain": "acme",
  "shopId": "sh_a1b2c3...",
  "data": { /* event-specific */ }
}
FieldDescription
idUnique event ID. Use for idempotency.
typeEvent name. Stable across versions.
createdAtWhen the event fired server-side (not when it was delivered).
shopSubdomainShop slug — useful for routing in multi-shop integrations.
shopIdShop UUID — stable across subdomain changes.
dataEvent-specific. See sections below.

Idempotency

The same id may be delivered more than once (at-least-once semantics; see Delivery and retries). Deduplicate by storing recent ids and dropping replays.

async function handle(event: WebhookEvent) {
  const seen = await redis.set(`webhook:${event.id}`, "1", { NX: true, EX: 86400 * 7 })
  if (!seen) return // already processed
  switch (event.type) { /* … */ }
}

Order events

order.created

Fired when a customer completes checkout. The order is PENDING (or CONFIRMED for non-COD payment methods).

{
  "type": "order.created",
  "data": {
    "order": {
      "id": "ord_a1b2c3...",
      "orderNumber": "ACME-1042",
      "status": "PENDING",
      "subtotal": "2980.00",
      "shippingCost": "60.00",
      "discountAmount": "0.00",
      "total": "3040.00",
      "paymentMethod": "COD",
      "customer": {
        "id": "u1b2...",
        "name": "Rafiul Hassan",
        "email": "rafiul@example.com",
        "phoneNumber": "+8801711000000"
      },
      "shippingAddress": { "name": "...", "phone": "...", "addressLine1": "...", "city": "Dhaka", "district": "Dhaka", "country": "BD" },
      "items": [
        { "productId": "...", "name": "Emerald Kurti", "quantity": 2, "price": "1490.00", "total": "2980.00" }
      ],
      "createdAt": "2026-05-27T13:45:00.000Z"
    }
  }
}

order.updated

Fired when an order's metadata changes (customer note added, address edited before shipping).

{
  "type": "order.updated",
  "data": {
    "orderId": "ord_a1b2c3...",
    "changes": ["customerNote", "shippingAddress"],
    "order": { /* full order object */ }
  }
}

order.status_changed

Fired on every status transition. The from and to fields are the previous and new values from the order lifecycle.

{
  "type": "order.status_changed",
  "data": {
    "orderId": "ord_a1b2c3...",
    "from": "PENDING",
    "to": "CONFIRMED",
    "changedAt": "2026-05-27T14:00:00.000Z",
    "changedBy": "user_a1b2..."
  }
}

Convenience derived events also fire alongside this one for the common transitions:

  • order.confirmedto: "CONFIRMED"
  • order.shippedto: "SHIPPED"
  • order.deliveredto: "DELIVERED"
  • order.cancelledto: "CANCELLED"
  • order.refundedto: "REFUNDED"
  • order.disputedto: "DISPUTED"
  • order.on_holdto: "ON_HOLD"

Subscribe to whichever fits your handler best. Use order.status_changed if you want the full transition log.

order.fulfilled

Fired when a courier is assigned and tracking info is generated. Includes the courier's tracking ID.

{
  "type": "order.fulfilled",
  "data": {
    "orderId": "ord_a1b2c3...",
    "courier": "STEADFAST",
    "trackingId": "STF-12345",
    "trackingUrl": "https://steadfast.com.bd/track/STF-12345",
    "fulfilledAt": "2026-05-27T15:00:00.000Z"
  }
}

Inventory events

inventory.adjusted

Fired on every POST /v1/admin/inventory/adjust call (manual or via API) and on every checkout that decrements stock.

{
  "type": "inventory.adjusted",
  "data": {
    "productId": "f3a7...",
    "variantId": null,
    "delta": -2,
    "previousStock": 12,
    "newStock": 10,
    "reason": "order:ord_a1b2c3...",
    "source": "checkout"
  }
}

inventory.low_stock

Fired once per crossing when a product or variant's stock drops at or below its lowStockThreshold (set per product; default 5).

{
  "type": "inventory.low_stock",
  "data": {
    "productId": "f3a7...",
    "variantId": null,
    "stock": 4,
    "threshold": 5
  }
}

Not re-fired until stock goes above the threshold and back below it.

inventory.out_of_stock

Fired when stock hits exactly zero.

{
  "type": "inventory.out_of_stock",
  "data": {
    "productId": "f3a7...",
    "variantId": null
  }
}

Product events

product.created

{
  "type": "product.created",
  "data": { "product": { /* full Product object (admin shape) */ } }
}

product.updated

{
  "type": "product.updated",
  "data": {
    "productId": "f3a7...",
    "changes": ["price", "comparePrice"],
    "product": { /* full product */ }
  }
}

product.deleted

{
  "type": "product.deleted",
  "data": { "productId": "f3a7..." }
}

Customer events

customer.created

{
  "type": "customer.created",
  "data": { "customer": { "id": "u1b2...", "email": "...", "name": "...", "phoneNumber": "..." } }
}

customer.updated

{
  "type": "customer.updated",
  "data": {
    "customerId": "u1b2...",
    "changes": ["phoneNumber"],
    "customer": { /* full customer */ }
  }
}

Cart events

cart.abandoned

Fired by the abandoned-cart cron when a cart has been idle for ≥4 hours and contains items.

{
  "type": "cart.abandoned",
  "data": {
    "cartId": "ca1b2...",
    "customerId": "u1b2...",
    "subtotal": "2980.00",
    "items": [/* line items */],
    "lastActivityAt": "2026-05-27T09:00:00.000Z"
  }
}

Webhook administration events

webhook.failed

Meta-event fired when a webhook delivery exhausts retries. Lets you build a "webhooks are broken" alert.

{
  "type": "webhook.failed",
  "data": {
    "webhookId": "wh_a1b2...",
    "deliveryId": "del_a1b2...",
    "originalEvent": "order.created",
    "lastResponseCode": 502,
    "attempts": 8
  }
}

Subscribe a different webhook to this event — subscribing the failing webhook to its own failures defeats the purpose.


Event name stability

Event names (order.created, inventory.adjusted, etc.) are part of the v1 contract and will never be renamed. New events ship without notice; field shapes within an event payload may add fields (additive) but never rename or remove them in v1.

On this page