Skip to content
New Webhooks added: Inventory and Order modifications. Check the changelog →
Cartly Developers

Webhooks

Receive real-time notifications when events occur in a Cartly store.

Available Events

Subscribe to these webhook event types. Send test also delivers a webhook.test event, which you cannot subscribe to.

NameTypeRequiredDescription
order.createdeventNoFired when a new order is placed
order.fulfilledeventNoFired when an order is marked as fulfilled
order.cancelledeventNoFired when an order is cancelled
order.paideventNoFired when payment is confirmed
product.createdeventNoFired when a product is created
product.updatedeventNoFired when a product is modified
product.deletedeventNoFired when a product is deleted
customer.createdeventNoFired when a customer account is created through the Headless Storefront API or the Apps API
app/uninstalledeventNoFired when your app is uninstalled; it is the last event the app receives

Delivery Headers

Every delivery carries the same headers: the first attempt, a retry from the failed-delivery queue and a Send test.

  • X-Cartly-Signature-V2: t=<unix seconds>,v1=<hex>, the HMAC-SHA256 of t + "." + raw body with your secret. Verify this one; it binds a timestamp, so a replayed request can be rejected
  • X-Cartly-Timestamp: the same t
  • X-Cartly-Hmac-Sha256 (alias X-Cartly-Signature): sha256=<hex>, the HMAC of the raw body only. Deprecated: it has no replay protection
  • X-Cartly-Event-Id: the id of the event, the same on every delivery of that event. Dedupe on it
  • X-Cartly-Webhook-Id: the subscription the delivery is for
  • X-Cartly-Shop-Domain: the store address, <slug>.mycartly.pro

The signature headers are sent when the subscription has a secret. Content-Type: application/json and User-Agent: Cartly-Webhook/1.0 are always sent.

Verify Webhook Signature (X-Cartly-Signature-V2)

javascript
const crypto = require('crypto');

// header = X-Cartly-Signature-V2: "t=<unix seconds>,v1=<hex>"; rawBody = the request body bytes as received
function verifyWebhook(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
  const got = Buffer.from(parts.v1 || '', 'hex');
  const want = Buffer.from(expected, 'hex');
  return got.length === want.length && crypto.timingSafeEqual(got, want);
}

Delivery and Retries

Cartly sends each event once to each subscribed endpoint and waits up to 10 seconds for a 2xx response. Redirects are not followed: a 3xx response is a failed delivery, so register the final URL. Any other status, a timeout or a refused connection is a failed delivery too. Send test and a retry from the failed-delivery queue wait up to 30 seconds, so a slow endpoint can pass Send test and still time out on live deliveries.

A failed delivery is kept in the store's failed-delivery queue (the dead-letter queue, under Settings → Webhooks in the admin), where it can be retried or discarded. A retry carries the original X-Cartly-Event-Id.

After 5 failed deliveries in a row spanning at least an hour the subscription is automatically disabled. A short outage of your endpoint does not switch it off, and a delivery that goes through resets the count. A successful Send test or retry re-enables a disabled subscription. Events that happen while a subscription is disabled are not queued for it.

Expected Response

Return a 2xx within 10 seconds and process the event asynchronously. The same event can arrive twice (a retry after a timeout): make your handler idempotent on X-Cartly-Event-Id.

Managing Webhooks in the Admin

Merchants manage subscriptions under Settings → Webhooks (/admin/webhooks):

  • Status: Active, Failing while deliveries are failing, or Disabled after the auto-disable rule above. A failing or disabled subscription shows its last error.
  • Send test sends one signed webhook.test event to the endpoint, without retries. If the endpoint answers 2xx, the subscription is active again.
  • Failed deliveries lists what your endpoint did not accept, with Retry, Discard and Retry All. The list is paginated.

When an App Is Uninstalled

When a store uninstalls an app, the app's access tokens are revoked and its webhook subscriptions stop receiving events right away, including REST hooks created by integrations such as Zapier and Make. Their queued failed deliveries are discarded. The one exception is a subscription to app/uninstalled: it still receives that single event and is then removed. The event data identifies the store and the listing (shop_id, listing_id, listing_slug).