Sanity webhook to Sume: reuse its idempotency-key header
Sanity delivers at least once with an idempotency-key header. Prefix it and pass it as the Sume Idempotency-Key so a repeated delivery is one job.

When a Sanity document is published and you want a Sume cover image, take the idempotency-key header Sanity attaches to every delivery and send it, with a prefix, as the Sume Idempotency-Key. Sanity delivers at least once, so the same publish can reach you more than once; the same key makes every copy resolve to one Sume job.
Sanity's webhooks are configured with a GROQ filter and projection, which is the right place to decide which documents trigger generation and to send only the fields the prompt needs.
What Sanity documents
The webhooks page describes a 30-second request timeout, two retries spaced 30 seconds apart, and retry on 429 and 5xx while other 4xx codes are treated as undeliverable. It also says an idempotency-key header is included so the receiver can dedupe, and that delivery is at-least-once. Signing is described as following the same standard as Stripe, through Sanity's webhook toolkit; use that toolkit for verification rather than writing your own.
The 30-second window is longer than the Sume synchronous wait, which is clamped to 30 seconds, but it is not a reason to block. Submit with mode: "async" and answer 200 immediately. A 4xx from your handler will not be retried by Sanity, so return 5xx only when you want another attempt.
| Sanity behavior | Value | Consequence |
|---|---|---|
| Timeout | 30 seconds | Still submit async and return |
| Retries | 2, 30 seconds apart | Window is about a minute |
| Retried statuses | 429 and 5xx | Return 503 on a Sume 429 |
| Other 4xx | Undeliverable | Return 200 for a bad prompt and log it |
| Dedupe header | idempotency-key | Prefix and forward to Sume |
| Guarantee | At least once | Never assume a single delivery |
The submit step
This runs after you have verified the delivery with Sanity's toolkit. It takes the verified header and the projected document title and returns the status code your endpoint should answer with.
import os, requests
def submit_cover(idempotency_key: str, title: str) -> int:
if not idempotency_key:
return 400
try:
r = requests.post(
"https://api.sume.com/v1/images",
headers={
"Authorization": f"Bearer {os.environ['SUME_API_KEY']}",
"Idempotency-Key": f"sanity-{idempotency_key}",
},
json={"model": "sume/auto", "mode": "async",
"prompt": f"Editorial cover illustration for: {title}"},
timeout=20,
)
except requests.RequestException:
return 503
if r.status_code in (202, 200):
return 200
return 503 if r.status_code in (429, 503) or r.status_code >= 500 else 200Where the key can mislead
Sanity's header identifies a delivery, and its retries reuse it, but a second publish of the same document is a new event with a new key. If you want exactly one cover per document no matter how many times it is edited, key on the document id instead and accept that later edits will not regenerate. Pick which behavior you mean before wiring the filter, because Sume will answer a changed prompt under a reused key with 409 idempotency_conflict.
Sources
Related posts
More in Integrations
- Shopify Analytics notes: log each AI video launch with its job id
Shopify Analytics now takes your own notes. Record when an AI product clip went live and its Sume job id, so a later conversion shift has a cause.
- Shopify Events dropped shopify-event-id: build your own Sume job key
Shopify Events deliveries no longer carry shopify-event-id or shopify-resource-id. If you used one as a Sume Idempotency-Key, build a new key from the resource.
- Shopify's Generate image: unsaved images vanish, so click Keep
Shopify's file picker can generate images, but unsaved ones are discarded unless you click Keep. How that differs from a Sume job result URL you retrieve later.
- Shopify Events GA: start a Sume product video from the payload query
Shopify's Next Gen Events are GA in API version 2026-10 with 18 topics and a query in the payload. One delivery can start a Sume job without a follow-up fetch.
Written by Sume