Webhook providers do not promise single delivery. Most operate on an "at least once" guarantee, which means the same event will eventually arrive more than once, and your handler has to be built for that (Hookdeck). The sending side makes this concrete. Svix retries a failed webhook delivery immediately, then at 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, and twice more at 10-hour intervals, giving receivers more than a day to recover (Svix). SendGrid, as one example, retries with exponential backoff for up to 24 hours and applies a 10-second response timeout (Hookdeck).
The receiving side has its own duplicate source: you. A handler that times out at 10 seconds, finishes the work at 25 seconds, and then receives the provider's retry will run twice unless it checks. A retry button in a browser client or an agent that calls a tool twice has the same effect. Stripe's own documentation frames the problem clearly: a timeout leaves the original outcome unknown, and an operation that already ran on the server may be repeated by a naive retry (Stripe). Idempotency keys close that hole.
A useful distinction: retries are the sender's job (delivery assurance), keys are your job (operational consistency). You need both (2POINT).



