Synthetic Industry

Troubleshooting guide · updated 2026-10-11

Slack events hit your endpoint twice, or not at all: signature, timestamp and the three-second rule

How Slack signs events, why the raw body and a five-minute window matter, and how to reply in time without running the work twice.

Setup handshake and what comes after

When you enter the request URL in the app's event settings, Slack posts a url_verification request containing a random challenge. Your endpoint must answer 200 with that challenge value, as plain text, form-encoded or JSON. After that, real events arrive as event_callback payloads. A failing handshake is a common first symptom and usually means the route is unreachable, redirects, or the framework rejected an unexpected content type.

  • The setup request needs no scope or event subscription, so a failure there is about reachability, not permissions.
  • Slack also checks the endpoint's certificate.
  • Follow redirects sparingly: Slack follows at most two.

Verify the signature on the raw bytes

Each request has an X-Slack-Signature and an X-Slack-Request-Timestamp header. Build the base string by joining the version label v0, the timestamp and the raw request body with colons, compute an HMAC with SHA-256 using your signing secret as the key, prefix the hex digest with v0= and compare it with the header using a function meant for comparing signatures. The body must be the exact bytes received; a framework that parses and re-serialises it first will make genuine requests fail.

  • Treat the signing secret as plain UTF-8 text from your app's settings.
  • Slack's own SDKs can do this for you when given the secret.
  • Regenerate the secret in the app settings if it is exposed.

The timestamp is the replay protection

The signature binds the timestamp to the body, so an attacker who captured a genuine request cannot change either without breaking it. They can, however, resend it unchanged. Slack's documentation says to drop a request whose timestamp is more than five minutes from your clock. That limits how long a captured request stays useful, and it makes your server's clock part of your security, so check that it is synchronised.

  • Reject too-old and too-new timestamps.
  • A request inside the window can still be repeated; claim the event ID before queueing the work (next section), and only after the signature and timestamp checks pass.
  • Test both edges of the window.

Reply first, work second, once

Slack requires a 2xx within three seconds, or the delivery counts as failed and is retried up to three times, the first retry nearly immediately. If your handler calls a slow service before replying, Slack retries and the work runs again. Reply immediately and queue the work. Record the event_id so a repeat does nothing, but record it first, not last: if the record is written only when the work finishes, the near-immediate retry arrives while the first job is still running, passes the "not processed yet" check, and a second job starts. The safe order is to verify the signature and timestamp, then claim the event_id with one atomic insert (a unique key on event_id with a state of received), then queue the job from that record, then reply 2xx. A delivery whose insert finds an existing claim replies 2xx and queues nothing. The job marks the row done when it finishes. Slack's retries carry retry-number and retry-reason headers, which help diagnose timeouts but should not be your only duplicate defence.

  • event_id is unique across workspaces, so it is a sound key.
  • Claim only after verification passes, so a forged request cannot use up a genuine event ID.
  • If the claim cannot be written, reply with a server error so Slack retries; a success you cannot honour loses the event.
  • Crash after the claim: queue the job from the claim record (or in the same transaction), have workers hold a time-limited lease, and run a sweep that re-queues or flags claims still received after a timeout longer than the job's longest run.
  • A worker that dies after doing the work but before marking it done will be re-run by the sweep, so make the action safe to repeat or protect it at the service it calls; the claim alone cannot promise exactly once.
  • Heavy failure rates can lead Slack to pause subscriptions for busier apps.
  • Buttons and slash commands have their own acknowledgement rules.

How the paid outcome is accepted

The Slack events receiver outcome is accepted when the handshake succeeds, forged, altered and stale requests start no work and claim no event ID, a ten-second downstream delay does not delay the reply, a repeated event ID is not processed again, two identical deliveries at the same moment queue one job, a worker stopped after the claim is recovered by the sweep so the action happens once, and an unavailable store gets an error reply rather than a success. If your endpoint is for a sender other than Slack, the generic signed-webhook receiver job is the fit; if it only rejects genuine events as unsigned, the endpoint repair job is the smaller scope. The fixed £445 price is untested and payment follows your sign-off. You hold the signing secret.

Sources and limits

  • Slack: verifying requests Checked 2026-10-11.
    • The signature is HMAC-SHA256 of version, timestamp and raw body with the signing secret, prefixed v0=.
    • Requests more than five minutes from the local clock should be dropped as possible replays.
    • The raw body must be read before parsing.
  • Slack: Events API Checked 2026-10-11.
    • The app must return a 2xx within three seconds.
    • Slack retries up to three times (the first nearly immediately, then after one minute and after five minutes) with x-slack-retry-num and x-slack-retry-reason headers.
    • event_id is globally unique across workspaces.
    • Slack follows at most two redirects and checks the endpoint's certificate; event subscriptions can be temporarily disabled when more than 95 per cent of delivery attempts fail within 60 minutes, and apps receiving fewer than 1,000 events an hour are not automatically disabled.
  • Slack: HTTP request URL verification Checked 2026-10-11.
    • Slack sends a url_verification request with a challenge that must be echoed in a 200 response.