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.

5 min readSume
All posts

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.

Slack Events API delivery rules (read 2026-10-02)
RuleValue on Slack's page
Acknowledge within3 seconds, HTTP 2xx
RetriesUp to 3: near-immediate, 1 minute, 5 minutes
Retry headersx-slack-retry-num, x-slack-retry-reason
Delivery ceiling30,000 per workspace per app per 60 minutes
Subscriptions disabled whenMore 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

All Integrations posts

Written by Sume