Discord bot AI video generation: defer, then edit the reply
Defer the Discord interaction within 3 seconds, submit POST /v1/videos with a callback_url, then edit the reply when Sume's job webhook arrives.

To build a Discord bot that makes AI video, answer the slash command's interaction with type 5, DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, within 3 seconds, then submit POST /v1/videos with a callback_url. When Sume's signed job webhook arrives, edit the deferred reply with the video URL while the 15-minute interaction token is still valid.
Sume has no Discord bot; this is your own app's HTTP interactions endpoint calling Sume over HTTPS. The Sume facts come from the Video Generation and Webhooks docs, and the Discord facts from docs.discord.com, all read on 2026-09-27. Sume's delivery and signing rules are covered in Signed webhooks for Sume video runs.
What must the interactions endpoint do within 3 seconds?
With an Interactions Endpoint URL set, Discord POSTs each interaction to your server, and you must send an initial response within 3 seconds or the interaction token is invalidated. A video job takes far longer: Sume's docs say video generation typically takes 30 seconds to several minutes.
- Validate
X-Signature-Ed25519andX-Signature-Timestampon every request with your app's public key, and answer401when validation fails. Discord sends invalid signatures on purpose as routine checks and removes the URL of an app that fails them. - Answer a PING (
type: 1) with a PONG (type: 1). - Answer a command with
{ "type": 5 }. It acknowledges the interaction, and the user sees a loading state until you edit the response.
How does the bot start the video job?
After the deferred response, send the command option's value as the prompt. Store the interaction's application_id, token, and channel_id with the returned job id, because Sume's webhook names the job, not the interaction.
- An
Idempotency-Keybuilt from the interactionidmakes a retried submit return the original job instead of starting a second one. callback_urlmust be a public HTTPS URL. Localhost, private-network, and non-HTTPS URLs are rejected.- Each command starts a job billed to your workspace balance. Sume's docs say to validate user input and enforce your own authorization before forwarding requests, so decide which servers and roles may run it.
app.post("/interactions", express.raw({ type: "application/json" }), async (req, res) => {
if (!verifyDiscord(req)) return res.status(401).end("invalid request signature"); // Ed25519
const i = JSON.parse(req.body);
if (i.type === 1) return res.json({ type: 1 }); // PING -> PONG
res.json({ type: 5 }); // DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, inside 3 seconds
const r = await fetch("https://api.sume.com/v1/videos", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.SUME_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": "discord-" + i.id,
},
body: JSON.stringify({
model: "sume/auto",
prompt: i.data.options[0].value,
callback_url: "https://bot.example.com/hooks/sume",
}),
});
const job = await r.json();
if (!r.ok) return editReply(i.application_id, i.token, "Sume refused the job: " + job.error.code);
await saveJob(job.id, { appId: i.application_id, token: i.token, channelId: i.channel_id, at: Date.now() });
});How do I edit the reply when the video is ready?
Sume POSTs one job webhook when the job reaches a terminal state and makes up to 10 attempts, each with a 10-second timeout. Verify the raw body, store the event, answer 2xx right away, and treat job_id as your idempotency key.
PATCH /webhooks/{application_id}/{interaction_token}/messages/@originaledits the deferred response in place. Interaction tokens are valid for 15 minutes.- A job that finishes after the token expires needs Discord's Create Message endpoint instead. In a server channel, that call needs the
SEND_MESSAGESpermission. - On
job.completed, thepayload.artifacts[]entry whosetypeisvideocarries the URL. It is a public artifact undermedia.sume.com, so anyone who can read the channel can open it. verifyWebhookaccepts anysume-v1=entry during a secret rotation. A hand-rolled check must split the header on commas.
import { verifyWebhook } from "@sume-com/sdk";
app.post("/hooks/sume", express.raw({ type: "application/json" }), async (req, res) => {
const ok = await verifyWebhook({
body: req.body,
headers: req.headers,
secret: process.env.SUME_COM_WEBHOOK_SIGNING_SECRET,
});
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body);
const ctx = await claimJob(event.job_id); // your store; null if this job_id was handled
res.status(204).end(); // Sume allows 10 s per attempt
if (!ctx) return;
const video = event.status === "OK" && event.payload.artifacts.find((a) => a.type === "video");
const content = video ? video.url : "The video job failed.";
// Tokens last 15 minutes; leave a margin, then fall back to Create Message.
if (Date.now() - ctx.at > 14 * 60 * 1000) return createChannelMessage(ctx.channelId, content);
await fetch("https://discord.com/api/v10/webhooks/" + ctx.appId + "/" + ctx.token + "/messages/@original", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content }),
});
});How do Discord's and Sume's checks differ?
The bot verifies two inbound requests with two different schemes. Keep them on separate routes.
| Property | Discord interaction | Sume job webhook |
|---|---|---|
| Scheme | Ed25519 signature | HMAC-SHA256 |
| Headers | X-Signature-Ed25519, X-Signature-Timestamp | x-sume-webhook-signature, x-sume-webhook-timestamp |
| Signed bytes | Timestamp followed by the body | <timestamp>.<raw_body> |
| Key | Your app's public key | Workspace webhook signing secret |
| Answer within | 3 seconds | 10 s per attempt |
What are the limits?
Two of them come from the job side rather than from Discord.
- Sume sends terminal job events only, with no progress deliveries. Between the defer and the edit, Discord's loading state is the only progress the user sees unless you poll
GET /v1/jobs/{id}/status. - A delivery that never lands does not change the job. Read the finished job from
GET /v1/jobs/{id}/result, then edit the reply or post a message. - Interaction tokens last 15 minutes, while video generation can take several minutes depending on the model, resolution, and server load. Keep the Create Message fallback.
Sources
Related posts
More in Integrations
- Express raw body for webhook signatures and the 100kb limit
Mount express.raw with type application/json and a limit above 1 MiB on the Sume webhook route, then pass the raw Buffer to verifyWebhook.
- Gemini CLI MCP server: add Sume's hosted MCP
Add Sume's hosted MCP server to Gemini CLI with httpUrl and an API-key header read from your environment, then allowlist and confirm its tools.
- GitHub Actions: generate a release video with a Sume Format
Start a Sume Format run when a GitHub release is published, pass the notes as input, poll until the video is ready, and attach it to the release.
- Golang: verify a webhook signature with HMAC and hmac.Equal
Verify a Sume webhook in Go: read the body once with io.ReadAll, HMAC-SHA256 the timestamp and raw bytes, then hmac.Equal each sume-v1 entry.
Written by Sume