Slack Events API retries 3 times: keep one Sume run per event
Slack retries a missed Events API ack three times and can disable your subscriptions. Ack in 3 seconds and let an Idempotency-Key keep one Sume run per event.

If your Slack app starts a Sume run from an Events API event, answer Slack with a 2xx inside three seconds and start the run with an Idempotency-Key built from the event. Slack retries a missed acknowledgement up to three times, so without a stable key one message can become up to four paid runs.
This post is only about the Events API (messages, mentions, reactions). Slash commands have their own deadline and are covered in the slash command bot post.
What does Slack do when your app is slow?
Slack's Events API page says your app must respond with an HTTP 2xx within three seconds, otherwise the delivery attempt is marked failed. A failed delivery is retried up to three times: nearly immediately, after one minute, and after five minutes.
Each retry carries an x-slack-retry-num header (1, 2 or 3) and an x-slack-retry-reason header such as http_timeout, connection_failed, ssl_error or http_error. A response that includes x-slack-no-retry: 1 on a non-200 tells Slack not to retry that event.
| Rule | Value on Slack's page |
|---|---|
| Acknowledge within | 3 seconds, HTTP 2xx |
| Retries | Up to 3: near-immediate, 1 minute, 5 minutes |
| Retry headers | x-slack-retry-num, x-slack-retry-reason |
| Delivery ceiling | 30,000 per workspace per app per 60 minutes |
| Subscriptions disabled when | More than 95% of deliveries fail within 60 minutes |
Why is a retry dangerous when the handler starts paid work?
A Sume run is paid work. If your handler waits for the run, it blows the three-second window, Slack retries, and the retry starts the same run again. Even if you respond quickly, a retry that arrives while your first request is still being processed runs the same code twice.
Sume's answer is the Idempotency-Key header on the Format run call. The same key with the same body returns 200 with the original run and idempotency_hit: true instead of a new 202. The same key with a different body is 409 idempotency_conflict, and a concurrent duplicate can be 409 idempotency_key_in_use, which the errors page marks retryable after about a second.
How do I wire it so a retry is harmless?
Derive the key from what the event is about, not from the delivery. For a message that should become a video, that is the channel and message you are acting on, plus a version suffix you control. Keep the body byte-identical across retries, because a different body under the same key is a conflict.
Return 200 as soon as the run is accepted. Ask for a communication.webhook_url so Sume tells you when it finishes, and post the result into the thread from that webhook handler.
export async function startRun(messageKey: string, text: string) {
const res = await fetch("https://api.sume.com/v1/formats/acme/promo/runs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `slack-${messageKey}-v1`,
},
body: JSON.stringify({
instruction: text,
generation_spend_cap_usd: 5,
communication: { webhook_url: "https://example.com/hooks/sume" },
}),
});
if (res.status !== 200 && res.status !== 202) {
throw new Error(`sume ${res.status}`);
}
const { data } = await res.json();
return data.id as string; // store it; idempotency_hit is true on a replay
}What about the 95 percent rule and the hourly ceiling?
Slack states that if your app fails to respond successfully to more than 95% of deliveries within 60 minutes, its event subscriptions are temporarily disabled and you get an email. The usual cause is the same mistake: the handler does slow work before it answers. Acknowledge first, then work.
The 30,000 deliveries per workspace per app per hour ceiling matters for noisy channels. Filter the events you subscribe to, and put a cheap check in front of anything that spends money. A spend cap on each run (generation_spend_cap_usd, documented on the Format call page) bounds the damage of a bug that slips through.
What does Sume not do here?
Sume does not receive Slack events and has no Slack-specific endpoint in these docs. Your server owns the Slack side, including its signature check. When Sume's webhook arrives, verify it with the `verifyWebhook` helper using your Sume signing secret, which is a different secret from Slack's.
Sume's own delivery rules still apply on the way back: your endpoint has 10 seconds per attempt, redirects are not followed, and up to 10 attempts are made. Dedupe on request_id, which stays the same across retries.
Sources
Related posts
More in Integrations
- smolagents MCPClient: give a CodeAgent Sume's remote tools
Connect a smolagents CodeAgent to Sume's hosted MCP with MCPClient and the streamable-http transport, send an API key header, and wait on jobs correctly.
- Snap Marketing API ai_content_source: the AI declaration on Media
Snap added an ai_content_source attribute to the Media entity in its Marketing API on August 28, 2026. What the changelog says, and how to record it.
- Snapchat Spotlight API: SUBMITTED, LIVE or REJECTED, 160-char caption
A Spotlight post through Snap's Public Profile API returns SUBMITTED, LIVE or REJECTED and takes a 160-character description with a locale. Prep it with Sume.
- Snapchat MEDIA_EXPIRED: Story media dies 24 hours after creation
Snap Public Profile Story media objects expire 24 hours after creation, which causes MEDIA_EXPIRED. Generate with Sume, then upload and post in one run.
Written by Sume