Idempotent webhook handlers: a short checklist

Retries mean your handler will see the same event twice. A few habits that make duplicate deliveries harmless.

Sep 2025

Because BrowserPilot retries until your endpoint acknowledges a delivery, your handler must be idempotent: seeing the same event twice should produce the same result as seeing it once.

This is a hard requirement, not a nice-to-have. Networks are unreliable. Your endpoint might acknowledge success but crash before processing the work. The webhook retry will hit again. If your handler is not idempotent, you will process the event twice.

The standard approach: key on run id plus event type (run.completed, run.failed, etc.) and record what you have processed. Before doing any work, check if this exact event has been processed before. If it has, return success without doing anything. If it has not, do the work.

Verify the HMAC signature before processing anything. This is your only defense against spoofed webhooks. Reconstruct the signed content (usually the full JSON payload or a canonical form), compute HMAC-SHA256 with your endpoint secret, and compare to the signature header. If the signatures do not match, reject the request.

Return 2xx only after the work is durable, not before. If you return success but then crash, the retry will not help. Do the work (update the database, publish a message, write to a file), make sure it is persisted, then return 200 OK.

Do the slow work asynchronously if possible. Accept the webhook fast (verify the signature, save the event to a queue), return 200, and let a background worker do the actual processing. A handler that blocks for ten seconds gets retried when the requester times out. A handler that returns fast never times out.