Shopify product video AI API with products/create webhooks
Answer Shopify's products/create webhook within five seconds, run a Sume Format from a queue, then upload the MP4 to Shopify with a staged upload.

To make an AI video for each new Shopify product, subscribe to the products/create webhook, verify it and answer 200 within Shopify's five seconds, then start a Sume Format run from a queue with the product photos as attachments. When Sume's signed webhook returns primary_output_url, upload that MP4 to Shopify with stagedUploadsCreate and fileCreate, then attach the file to the product once it is READY.
Sume has no Shopify app; this is your own app server talking to Shopify's Admin API and to Sume over HTTPS. The Sume facts come from Create a run and Runs and results, and the Shopify facts from shopify.dev, all read on 2026-09-27. Which catalog Formats suit product video is covered in Ready-made Formats for product video.
How fast must my app answer Shopify's webhook?
Shopify allows a one-second connection timeout and five seconds for the whole request, and any response outside the 200 range, 3XX included, is an error. After 8 consecutive failures it deletes a subscription configured through the Admin API. Shopify's own advice for staying inside five seconds is a queue, so the receiver does three short things:
- Verify
X-Shopify-Hmac-SHA256, a base64 HMAC-SHA256 of the raw body keyed with your app's client secret. - Skip any
X-Shopify-Webhook-Idyou have already stored, because Shopify may deliver the same webhook more than once. - Enqueue the product and answer
200. Call Sume from the queue: a Format run's create fetches and copies every attachment before it answers.
How do I start the Sume run for a new product?
The worker calls a catalog Format at sume/{slug} with a formats:write key. Put product text such as the title in input, not instruction; Sume's docs name input as the place for product copy. Store data.id next to the product id, because a value you send cannot come back in output unless the run repeats it.
attachmentstakes up to 30 images, 30 MB each and 500 MB per run, as JPEG, PNG, WebP, GIF, or AVIF. Sume fetches them at create time, so each URL must be reachable without auth; an unreachable one fails the create with502 attachment_fetch_failedanddetails.index.- Derive
Idempotency-Keyfrom the product id and a version you bump for a deliberate re-render. The same key and body returns200with the original receipt andidempotency_hit: true, with no second charge. generation_spend_cap_usdcaps one product's run at up to $500;0is rejected.- Shopify's sample
products/createpayload has an emptyimagesarray. A product with no photos has nothing to attach yet.
// Queue worker. product = { id, title, imageUrls } from your queue.
async function startProductVideo(product) {
const res = await fetch("https://api.sume.com/v1/formats/sume/sume-product-commercial/runs", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.SUME_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": "shopify-product-" + product.id + "-v1",
},
body: JSON.stringify({
instruction: "Make a product video from the attached photos.",
input: { product_title: product.title },
attachments: product.imageUrls.slice(0, 30).map((url) => ({ type: "input_image", image_url: url })),
generation_spend_cap_usd: 20,
communication: { webhook_url: "https://app.example.com/hooks/sume" },
}),
});
const body = await res.json();
if (!res.ok) throw new Error(body.error.code); // e.g. attachment_fetch_failed
await saveRun(body.data.id, product.id);
}How do Shopify's webhook and Sume's webhook differ?
Your app receives two signed webhooks with two schemes and two secrets, so give each its own route and verifier. During a secret rotation, Sume's header carries two comma-separated sume-v1= entries for 24 hours; accept either, as verifyWebhook in @sume-com/sdk does.
| Property | Shopify `products/create` | Sume `format.run.terminal` |
|---|---|---|
| Signature header | X-Shopify-Hmac-SHA256, base64 | x-sume-webhook-signature: sume-v1=<hex> |
| Signed bytes | The raw body | <timestamp>.<raw_body> |
| Secret | App client secret | Workspace webhook signing secret |
| Answer within | 1 s to connect, 5 s in total | 10 s per attempt |
| Retries | 8 times over 4 hours | Up to 10 attempts |
| Dedupe on | X-Shopify-Webhook-Id | request_id |
| A 3xx answer | An error | A failed attempt, never followed |
How do I attach the finished video to the product?
Sume POSTs one format.run.terminal event when the run completes or fails. On status: "OK", payload.primary_output_url is a durable media.sume.com URL, and payload.artifacts[] lists each file with content_type, size_bytes, and duration_ms; check those against Shopify's video limits. Don't pass the Sume URL straight to fileCreate: Shopify's `FileCreateInput` takes an external URL only for images, generic files, or external (YouTube or Vimeo) videos, and a Shopify-hosted video needs a staged upload URL. Download the MP4 and upload it in steps:
- Request a target with
stagedUploadsCreate(mutation below). Shopify requiresfileSizefor videos; send the artifact'ssize_bytes. - POST the MP4 to the returned
urlas multipart form data, with the returnedparameters. - Call `fileCreate` with the
resourceUrlasoriginalSourceandcontentType: VIDEO. - Files are processed asynchronously. Poll
fileStatusuntil it isREADY(orFAILED), then attach the file withproductSet,productCreate, orproductUpdate, referencing it by ID. - A failed run arrives with
status: "ERROR". If the receipt was over 1 MiB,payloadisnullanderror.result_urlsays where to fetch it.
mutation {
stagedUploadsCreate(input: [{
filename: "product-video.mp4",
mimeType: "video/mp4",
resource: VIDEO,
fileSize: "899765"
}]) {
stagedTargets { url resourceUrl parameters { name value } }
userErrors { field message }
}
}What are the limits?
Shopify limits the file you import; Sume's rules shape the run that makes it.
- Shopify videos: MP4, MOV, or WEBM, up to 1 GB, 10 minutes, and 3840x2160. Apps can create up to 1,000 videos per store per week.
- A Format run over the API is unattended. Approvals a recipe would ask a person for are pre-granted, and a run that cannot finish comes back
failed. - Shopify's media guide assumes the
read_products,write_products, andwrite_filesaccess scopes. - Format run media URLs are public to anyone holding them. Proxy or copy them if you need per-customer access control.
Sources
- Create a run
- Runs and results
- Format API
- Format catalog
- Run webhooks
- Verifying webhooks
- Shopify: Verify webhook deliveries (read 2026-09-27)
- Shopify: Manage media for products and collections (read 2026-09-27)
- Shopify GraphQL Admin API: fileCreate (read 2026-09-27)
- Shopify GraphQL Admin API: FileCreateInput (read 2026-09-27)
- Shopify: Webhooks reference (read 2026-09-27)
Related posts
More in Integrations
- Slack bot to generate video: a slash command with Sume's API
Ack Slack's slash command within 3000 ms, submit POST /v1/videos with a callback_url, then post the URL to response_url when Sume's webhook lands.
- Cline MCP remote server: add Sume's hosted MCP
Add Sume's hosted MCP server to Cline as a remote server with type streamableHttp and an API-key header, and keep paid tools out of autoApprove.
- VS Code remote MCP server: add Sume's hosted MCP in mcp.json
Add Sume's hosted MCP server to VS Code with an http entry in mcp.json, see what Sume's OAuth consent grants, and choose which tools chat can call.
- Supabase Edge Function webhook for Sume: JWT off, HMAC on
Sume's webhook POST carries no Supabase JWT, so deploy the Edge Function with verify_jwt = false and check Sume's HMAC signature on every delivery.
Written by Sume