Shopify selling_plan_id in order webhooks: a Sume welcome clip
Shopify 10.01 order webhooks carry selling_plan_id on line items. Use it to start one Sume welcome video per subscription order, with a safe retry key.

How do you send a welcome video only to customers who bought a subscription? Shopify's 10.01 changelog says order webhook events now carry a selling_plan_id field on line item objects (read 2026-10-04). A line item with a selling plan is a subscription line, so your webhook handler can branch on that one field and start a Sume video job only for those orders.
The handler has to do two things well: answer Shopify quickly, and never create a second paid job when Shopify retries the delivery.
Branch on the line item
In an order payload, line_items is an array. Each entry that was bought through a subscription has a non-null selling_plan_id under the 10.01 shape. Loop the lines, keep the ones where that field is present, and create one clip per such line, or one per order if you prefer.
Do not infer a subscription from anything else, such as the product title. The field is the signal Shopify now provides, and an order with a mix of one-time and subscription lines will have both kinds in the same array.
| Field | Use |
|---|---|
id | Order id, part of the idempotency key |
line_items[].id | Line id, completes the idempotency key |
line_items[].selling_plan_id | Present when the line was a subscription purchase |
line_items[].title | Prompt text for the clip |
The handler and the retry key
Shopify redelivers a webhook when it does not get a timely success, so your handler will sometimes see the same order twice. Sume's Idempotency-Key makes that harmless: the retry returns the original job. Build the key from the order id and the line id so each subscription line maps to exactly one job. The function below runs against a sample payload, so you can test it before wiring it to a live topic.
It uses the image endpoint with model: "sume/auto" for a welcome still. Swap in a video request if that is your product; the key and the early return work the same way.
const sample = {
id: 1001,
line_items: [
{ id: 11, title: 'Coffee, monthly', selling_plan_id: 555 },
{ id: 12, title: 'Mug', selling_plan_id: null },
],
};
async function handleOrder(order) {
const key = process.env.SUME_API_KEY;
if (!key) throw new Error('SUME_API_KEY is not set');
const subs = order.line_items.filter((l) => l.selling_plan_id != null);
const out = [];
for (const line of subs) {
const res = await fetch('https://api.sume.com/v1/images', {
method: 'POST',
headers: {
'x-api-key': key,
'content-type': 'application/json',
'idempotency-key': 'order-' + order.id + '-line-' + line.id,
},
body: JSON.stringify({ model: 'sume/auto', prompt: 'Welcome card for ' + line.title }),
});
out.push(res.status);
}
return out;
}
handleOrder(sample).then((s) => console.log(s)).catch((e) => console.error(e.message));Answer fast, then wait for the job
Return a success to Shopify as soon as you have queued the work, not after the render. A Sume 202 means accepted, not finished. Store the job id against the order, then either poll GET /v1/jobs/:id/status, honoring next_poll_after_seconds, or use webhook mode and handle job.completed. Sume retries a webhook delivery up to 10 times, 30 seconds apart, with a 10 second timeout, so your receiver should answer quickly too and treat job_id as its own idempotency key.
The post on the five-second webhook limit and a hero image job shows how to split receive and work, and product video automation from a webhook is the broader walkthrough.
Caveats
Test against a real 10.01 payload before relying on the field, since older webhook API versions will not include it. Keep the Sume key on your server, rotate it if it leaks, and send only one auth header. Reusing an idempotency key with a different body returns 409 idempotency_conflict, so if you change the prompt template later, include a template version in the key.
Sources
Related posts
More in Integrations
- A Supabase job table for Sume webhooks: upsert on job_id
Sume retries webhooks up to 10 times. A Postgres table keyed on job_id with ON CONFLICT turns repeat deliveries into no-ops. Schema, SQL and the order of steps.
- ubuntu-latest moves to 26.04: test your Sume workflow now
GitHub is moving ubuntu-latest to Ubuntu 26.04 between Oct 19 and Nov 19. Run a Sume API smoke test on both images, or pin 24.04, before it flips.
- Vercel AI SDK tool search maxResults and Sume tool groups
ai@7.0.127 tool search ranks deferred tools with a search() callback and maxResults. How to split Sume's hosted MCP tools into always-on and deferred groups.
- Vercel AI Gateway's new tools and models, and where Sume media fits
AI Gateway added Browserbase tools and audio models. A planner model can route through a gateway while Sume handles the paid media jobs; where to draw the line.
Written by Sume