Webhooks

Webhooks let your server react to events the moment they happen. New order, cart abandoned, product updated, customer registered. Subscribe once, get a POST every time.

Quick start

  1. Open the dashboard, Settings, Webhooks.
  2. Add an endpoint. Paste the URL on your server that should receive events.
  3. Pick the event types you want.
  4. Copy the signing secret shown after creation. You will need it to verify deliveries.

Payload shape

Every delivery is a POST with a JSON body. The envelope is consistent across event types, only the data field changes.

{
  "id": "evt_01HXYZABC...",
  "type": "order.paid",
  "createdAt": "2026-04-20T13:24:11Z",
  "storeId": "str_01HXY...",
  "data": {
    "id": "ord_01HXYZDEF...",
    "orderNumber": "1024",
    "currency": "GBP",
    "totals": {
      "total": { "amount": 12400, "currency": "GBP", "formatted": "£124.00" }
    },
    "customer": { "email": "[email protected]", "firstName": "Sam", "lastName": "Buyer" },
    "items": [
      {
        "id": "oitm_01HXY...",
        "productId": "prod_01HXY...",
        "productName": "Hand-poured candle",
        "quantity": 2,
        "price":    { "amount": 2400, "currency": "GBP", "formatted": "£24.00" },
        "subtotal": { "amount": 4800, "currency": "GBP", "formatted": "£48.00" }
      }
    ]
  }
}

Always check the type field first. We add new event types over time and your handler should ignore types it doesn't recognise rather than 500.

Event types

イベントWhen it fires
order.createdOrder placed (may not be paid yet, e.g. bank transfer pending)
order.paidPayment confirmed
order.fulfilledMarked shipped or digital download delivered
order.refundedFull or partial refund issued
order.cancelledOrder cancelled before fulfilment
cart.abandonedCart inactive for 30 minutes with at least one item
customer.createdNew customer record (registered or first checkout)
product.createdProduct added to catalogue
product.updatedProduct details, price, or stock changed
product.deletedProduct removed from catalogue

Verifying signatures

Every request includes an X-Shoprocket-Signature header. Verify it before trusting the payload, otherwise anyone who knows your URL can fake events.

X-Shoprocket-Signature: t=1745158451,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

The signature is HMAC-SHA256 over the string {timestamp}.{raw_body}, keyed with your endpoint signing secret.

Node example:

import crypto from 'node:crypto';

function verify(rawBody, header, secret) {
  const [tPart, vPart] = header.split(',');
  const timestamp = tPart.split('=')[1];
  const sent = vPart.split('=')[1];
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(sent), Buffer.from(expected));
}

PHP example:

function verify(string $rawBody, string $header, string $secret): bool {
  [$t, $v] = array_map(fn($p) => explode('=', $p, 2)[1], explode(',', $header));
  $expected = hash_hmac('sha256', "{$t}.{$rawBody}", $secret);
  return hash_equals($expected, $v);
}

Retries and idempotency

Your endpoint must return a 2xx status within 10 seconds. Anything else is treated as a failure and the delivery enters retry.

Retry schedule: immediate, then 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 24 hours. After 24 hours we give up and the delivery is marked failed in your dashboard.

Securing your endpoint

The right defence is signature verification, covered above. HMAC checks every delivery against your endpoint secret. Anyone with your URL but not your secret is rejected.

If your security policy also requires an IP allowlist, contact support after launch and we will work with you on a fixed-IP delivery setup. The default delivery infrastructure uses dynamic source IPs that change with scaling, so signature verification is the only universally reliable check.

最終更新日: 27 4月 2026

Full changelog →

Stuck on something?

Our dev team replies fast. Email us with code samples or open chat for a quick question.

dev@shoprocket.io