Shopify X-Shopify-Webhook-Id as the Sume Idempotency-Key
Shopify retries a failed webhook 8 times over 4 hours. Derive the Sume Idempotency-Key from the webhook id so each delivery makes one run, never two.

When a Shopify webhook starts a Sume run, build the Idempotency-Key from the X-Shopify-Webhook-Id header. Shopify documents that header as the per-delivery id to deduplicate on, and it retries a failed delivery 8 times over 4 hours, so a retry will arrive again with the same id. Sume then returns the original run with idempotency_hit: true instead of starting a second paid one.
The Shopify facts are from About webhooks and Deliver webhooks through HTTPS, both read 2026-10-10. The Sume facts are from Create a run.
What does Shopify promise?
The delivery page lists a 1 second connection timeout and a 5 second total timeout. A failed delivery is retried 8 times over 4 hours, and after 8 consecutive failures a subscription created through the Admin API is deleted. The signature is a base64 HMAC in X-Shopify-Hmac-SHA256. There is no ordering guarantee: use X-Shopify-Triggered-At to compare events, and run a periodic reconciliation job.
Count the exposure. Eight retries over four hours means one product edit can reach your endpoint up to 9 times, the first delivery plus 8 retries. Without a stable key, each of the 9 could become a paid render. With one, the count of paid runs is 1.
| Item | Shopify says | Use with Sume |
|---|---|---|
| Timeouts | 1 s connection, 5 s total | Return 200 before calling Sume if you can |
| Retries | 8 over 4 hours | The same webhook id returns, so the key repeats |
| Subscription | Deleted after 8 consecutive failures (Admin API) | Monitor failures, not just success |
| X-Shopify-Webhook-Id | Per-delivery dedupe id | Part of the Idempotency-Key |
| X-Shopify-Event-Id | Correlates deliveries from the same action | Use to group, not to deduplicate a delivery |
| Ordering | Not guaranteed; use X-Shopify-Triggered-At | Reject an older event than the last you rendered |
How do I build the key?
Take the webhook id and prefix it with the Format or step, for example shopify-<webhook id>. The Sume key is scoped to one Format and can be up to 255 characters, so the id fits. Do not use the clock. A key made from the time never repeats, and the whole point is that a retry does.
A word of care: the same key with a different body returns 409 idempotency_conflict. A retried Shopify delivery carries the same payload, so that should not happen. If you add fields to the Sume body from live data, such as the latest price, you create the conflict yourself. Build the body from the webhook payload only.
What about the 5 second limit?
The total timeout of 5 seconds is short for an HTTP call to a render API plus your own processing. Verify the HMAC, store the webhook id and payload, return 200, and make the Sume call from a worker. Reading the raw body for the HMAC matters here; base64 of the digest is compared with the header.
If the worker fails after the 200, Shopify does not retry. Your own queue has to. The idempotency key makes that retry safe, because Sume answers the repeat with the original run.
Log the webhook id with the Sume run id on the same line. When a merchant asks why a render happened, one search answers it, and you can tell a retry from a genuine second edit.
What does reconciliation look like?
Shopify says to run a periodic reconciliation, since delivery is not guaranteed in order or even at all after repeated failure. A nightly job lists products changed since the last pass and submits runs with a key such as product-<id>-<updated_at>. If the webhook already started that render, the key matches and Sume returns the existing run. The two paths therefore cannot double-bill each other.
Keep a spend cap on the request with generation_spend_cap_usd, since a store with a large catalog can queue many products at once.
Sources
Related posts
More in Integrations
- Slack link unfurling for a page that shows a Sume render
A Slack app can unfurl your own link with a Sume render preview using link_shared and chat.unfurl. Learn the 5-domain limit and the reinstall rule first.
- Slack scheduleMessage: announce a finished Sume render
Slack lets a bot schedule a post up to 120 days ahead, with at most 30 per 5 minutes in a channel. Finish the Sume render first, then schedule the announcement.
- Slack trigger_id expires in 3 seconds: open the modal first
A Slack trigger_id works once and only for 3 seconds. Open the modal first, then call Sume, and reply later through response_url within 30 minutes and 5 uses.
- Smartsheet webhook verification challenge, then a Sume Format run
Smartsheet checks your endpoint with a challenge before a webhook is enabled. Answer it, then use change events to start Sume Format runs with a stable key.
Written by Sume