Skip to content
Webhooks SDK
Esc
navigateopen⌘Jpreview
On this page

Handshakes

Most providers won't deliver anything until you answer a one-time challenge — and the right order differs.

Most providers will not deliver anything until you answer a one-time challenge. This is the part most webhook code forgets — and it’s why a freshly deployed endpoint that “never receives events” is usually an endpoint that never answered its setup probe.

The SDK answers handshakes for you. A handshake short-circuits before dispatch and reports outcome: 'handshake', so your event handlers never see it.

Two classes, and the order matters

Unsigned challenges — Meta’s hub.challenge, Asana’s X-Hook-Secret — must be answered before verification, because the request is often what establishes the secret. There is nothing to verify against yet.

Signed challenges must be verified first. Answering them before verifying is a security hole — and sometimes a setup failure: Discord probes a new endpoint with a deliberately invalid signature and refuses to save the URL unless it receives a 401.

Providers declare which class they are, and the handler orders the two calls to match. You don’t configure this; it’s part of each provider’s definition.

What the shipped providers expect

Provider Trigger Expected answer Signed?
Discord (interactions) POST with type: 1 (PING) { "type": 1 } ✅ must reject a bad signature with 401
Discord (webhook events) POST with type: 0 (PING) bare 204 ✅ same
Google Pub/Sub none — but the endpoint must return 2xx fast ack within the deadline or it redelivers n/a
Stripe, GitHub, Twilio, Standard Webhooks none

Observing handshakes

If you process requests manually, the result tells you when a handshake was answered:

const result = await handler.process(request)

if (result.outcome === 'handshake') {
  // setup probe answered; no event was dispatched
}

Was this page helpful?