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.

To build a Slack bot that generates video, answer the slash command with HTTP 200 inside Slack's 3000 ms, then submit POST /v1/videos with a callback_url on your server. When Sume's signed job webhook arrives, post the media.sume.com video URL to the command's response_url, or with chat.postMessage once 30 minutes have passed.
Sume has no Slack app; this is your own Slack app's server calling Sume over HTTPS. The Sume facts come from the Video Generation and Webhooks docs, and the Slack facts from docs.slack.dev, all read on 2026-09-27. Sume's signing scheme is covered in Signed webhooks for Sume video runs.
Why must the bot answer Slack before it calls Sume?
Slack sends a slash command as an HTTP POST with application/x-www-form-urlencoded data, and your app must answer with HTTP 200 within 3000 milliseconds or the user sees an operation_timeout error. Anything the bot does before answering spends that budget, and the video itself takes far longer: Sume's docs say video generation typically takes 30 seconds to several minutes. So acknowledge first, submit next, and post the video when it is ready.
- Verify the request before anything else. Slack signs
v0:{timestamp}:{body}with your app's signing secret and sends it asX-Slack-Signature; reject anyX-Slack-Request-Timestampmore than five minutes from local time. - The 200 body can carry a message such as “Rendering your video…”.
ephemeralis the defaultresponse_type, so only the person who ran the command sees it. - If the submit fails later, report it as a message, not an HTTP 500. Slack says the status code only tells it whether the payload was received.
How does the bot start the video job?
After the acknowledgment, send the command's text as the prompt. Store the command's response_url and channel_id with the returned job id: Sume's webhook names the job, not the Slack conversation, so your store links the two.
- An
Idempotency-Keymakes a retried submit safe, because a replay returns the original job. Slack'sresponse_urlis unique to each payload, so a hash of it is a stable key. 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, and Sume's docs say to validate user input and enforce your own authorization before forwarding requests. Allow-list the
user_idorchannel_idvalues you accept.
import crypto from "node:crypto";
app.post("/slack/video", express.raw({ type: "application/x-www-form-urlencoded" }), async (req, res) => {
if (!verifySlack(req)) return res.status(401).end(); // v0 signature, 5-minute window
const cmd = Object.fromEntries(new URLSearchParams(req.body.toString("utf8")));
res.json({ response_type: "ephemeral", text: "Rendering your video…" }); // inside 3000 ms
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": "slack-" + crypto.createHash("sha256").update(cmd.response_url).digest("hex"),
},
body: JSON.stringify({
model: "sume/auto",
prompt: cmd.text,
callback_url: "https://bot.example.com/hooks/sume",
}),
});
const job = await r.json();
if (!r.ok) return reply(cmd.response_url, "Sume refused the job: " + job.error.code);
await saveJob(job.id, { responseUrl: cmd.response_url, channelId: cmd.channel_id, at: Date.now() });
});How do I post the video when Sume's webhook arrives?
Sume POSTs one job webhook to callback_url when the job reaches a terminal state, and makes up to 10 attempts with a 10-second timeout each. Verify it on the raw body, store it, answer 2xx right away, and treat job_id as your idempotency key so a repeat delivery posts nothing twice.
- Check
x-sume-webhook-signaturewithverifyWebhookfrom@sume-com/sdk, which accepts anysume-v1=entry during a secret rotation. A hand-rolled check must split the header on commas. - On
job.completed, take thepayload.artifacts[]entry whosetypeisvideo. Itsurlis a public artifact undermedia.sume.com, so anyone in the channel can open it. - Within 30 minutes of the command, POST
{ "response_type": "in_channel", "text": url }to the storedresponse_url. Slack accepts up to 5 responses there in that window. - After 30 minutes, publish with `chat.postMessage`, a bot token with
chat:write, and the storedchannel_id. Slack generally allows 1 message per second per channel, and a new app needschat:write.publicto post in all public channels. - On
job.failed(status: "ERROR"), post a short failure note the same way.
How do Slack's and Sume's signatures differ?
The bot verifies two inbound requests with two different secrets. Keep the verifiers separate.
| Property | Slack slash command | Sume job webhook |
|---|---|---|
| Signature header | X-Slack-Signature: v0=<hex> | x-sume-webhook-signature: sume-v1=<hex> |
| Timestamp header | X-Slack-Request-Timestamp | x-sume-webhook-timestamp |
| Signed string | v0:{timestamp}:{body} | {timestamp}.{raw_body} |
| Secret | App signing secret | Workspace webhook signing secret |
| Replay window | 5 minutes | 5 minutes, the suggested default |
| Answer within | 3000 ms | 10 s per attempt |
What are the limits?
A few rules shape the bot beyond the two deadlines.
- Slash commands created by developers cannot be invoked in message threads.
- Sume sends terminal job events only, with no progress deliveries. For a “still rendering” update, poll
GET /v1/jobs/{id}/statusyourself. - A delivery that never lands does not change the job. Read the finished job from
GET /v1/jobs/{id}/resultand post from there.
Sources
- Video Generation
- Webhooks
- Verifying webhooks
- Jobs and results
- Core workflow
- Authentication
- Slack: Implementing slash commands (read 2026-09-27)
- Slack: Handling user interaction in your Slack apps (read 2026-09-27)
- Slack: Verifying requests from Slack (read 2026-09-27)
- Slack: chat.postMessage method (read 2026-09-27)
Related posts
More in Integrations
- 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.
- Telegram bot to generate video: a Sume job, then sendVideo
A Telegram bot can turn /video into a Sume job, reply at once, then call sendVideo with the artifact URL when Sume's signed webhook arrives.
Written by Sume