Knowledge baseAugust 14, 2026·updated August 14, 2026

Webhooks: how to receive events and not lose them

The webhook delivers the event in seconds, but guarantees it will happen no more than once "at least once" - with duplicates, no order, and silent loss after exhausting repetitions. Analysis of the receiver that experiences this: signature, log of raw events, queue and reconciliation.

What does this solve?

Requesting someone else's API on a schedule pays three times: a delay until the next cycle, limits on the number of requests, and tons of empty responses where nothing has changed. The webhook reverses the direction - the supplier himself knocks on the specified address at the time of the event. Payment is confirmed, the build fails, the deal changes stages, the letter is delivered: the receiver finds out about this in seconds and spends resources only on the case. Pay for speed - the receiver becomes part of someone else's production, and the requirements for it are the same as for a combat service: availability, response speed, resistance to duplicates.

How it works

Subscription

The receiver address is registered in the supplier’s account or its API. Usually, a list of event types is selected there and a shared secret is set. Good form - there is a separate path for each supplier (/hooks/<имя>), because the body format, signature scheme and repetition rules are different for everyone, and it’s painful to parse this in one handler through a set of conditions.

Delivery

The event leaves with an HTTP request, more often a POST with a JSON body. The headers usually contain the event identifier, its type, signature and timestamp. The response 2xx is considered a confirmation. Any other code or timeout starts retries at increasing intervals: a few minutes, then hours, then failure. Some suppliers, after a series of failures, simply turn off the subscription - and they find out about their silence within a week.

What is guaranteed and what is not

The practical standard for delivery is “at least once.” From this follow two facts that determine the entire design of the receiver. Duplicates are normal: the network was interrupted after processing, but before the response, and the same event will come again. The order is not guaranteed: with parallel delivery, “order cancelled” may well arrive before “order created,” especially after a series of repetitions. The object's state is therefore calculated from the event data and timestamps, rather than from its arrival.

Signature

The signature is an HMAC-SHA256 from the exact bytes of the request body on the shared secret, sometimes with a timestamp added. Three rules that people stumble over regularly. You need to count from the raw body, read before parsing: the framework that parsed the JSON and put it back together changes the order of the keys and spaces, and the signature no longer matches. You need to compare using a constant time function, not the usual string equality. The timestamp is checked for freshness, otherwise a valid request intercepted by someone is replayed as many times as necessary. A separate trap is the register and the exact composition of parameters in schemes where the signature is calculated not by the body, but by the list of fields: an extra or missing field gives the same error as an incorrect secret, and the message will be about “object not found.”

Thick body or thin notice

Some providers send the full object, others only send the event ID and type. The second option (“notified - go pick it up”) is more reliable: it does not depend on the order of delivery, because the API will always return the current status, and does not carry around personal data again. The cost is an additional request and dependence on the provider's API limits.

Correct handler form

Four steps, and the order in them is important. Check the signature and discard someone else's. Write the entire event to a log or queue - raw body, headers, time of receipt. Reply 200. Process asynchronously with a separate worker. Processing inside an HTTP request is limited by the provider's timeout (usually a few seconds) and turns any brake on the external system into an avalanche of retries.

Idempotency is ensured by a unique index on the event ID in the log. A double arrives - the insert falls off, the handler responds with 200 and does nothing. If the provider does not send an identifier, the key is collected from business fields: event type, object, timestamp.

What to do with failures

Unparsed events accumulate in the queue with the number of attempts. After the attempts are exhausted, they go into a separate sump and are not deleted: almost always there is either a crooked format that no one expected, or a bug in the handler, and after fixing the pack is replayed by one team. On top of this, periodic reconciliation is needed: once a day, a list of objects for the period is pulled from the provider’s API and compared with what is actually accepted.

Useful scripts

  • Payment gateway: confirmation of payment, return, refusal of subscription - with mandatory verification of the amount and currency according to your order record.
  • CI/CD: an event about the completion of a build or a merge request triggers a deployment or notification in the chat.
  • Messengers and mail: incoming messages and letter delivery statuses arrive as an event instead of polling the mailbox.
  • CRM and forms: changing the stage of a transaction or a new application instantly raises the task for the person in charge.
  • Logistics: statuses of shipments from the carrier are included in the order card without manual tracking.

Limitations

Delivery is not completely guaranteed. The repetitions are finite, and after they are exhausted, the event disappears. Without periodic reconciliation with the API, the hole goes unnoticed until the client asks where his paid order is.

Receiver downtime is directly converted into losses. The hour of unavailability is experienced by those suppliers whose repetitions are spread over a day; those who have three attempts in five minutes do not worry.

No order. The logic “if event B has arrived, then A has already been processed” breaks down on the very first retray.

The endpoint is public by definition. It is available to the entire Internet, and without signature verification and request rate limit, it is an open door: someone else's POST with a plausible body pretends that the payment went through.

Receiver domain certificate - point of silent failure. Expired - the supplier stops delivering and is often silent about it.

Debugging is inconvenient. The local machine is not accessible from the outside, so a tunnel is needed, and a specific event from yesterday can only be repeated if the supplier gives a replay from the office. The caught raw body should be saved for testing.

The secret requires rotation, and rotation requires windows where the old and new key are valid at the same time; This is not done alone by any supplier without a short period of double checking.

Response 200 means "accepted" only. It cannot be considered a report of successful processing: the success message is sent to the client after the actual recording, and not after reception.

How to check the result

Log of raw events with the fields “received”, “processed”, “status”, “number of attempts” - the first screen of any diagnostics. One request to it answers three questions at once: whether events arrive at all, whether they are processed, and where they are stuck.

Metric that is worth monitoring: the number of events in the unprocessed status that are older than several minutes. It catches a crashed worker, a frozen external system, and a new type of event that no one understands.

Double check: the same body is sent to the endpoint twice. There should be one record left on the target system, and the second query should return 200.

Signature verification: a body with a damaged signature must receive a refusal, and not a silent 200. Garbage is checked separately - an empty body, non-JSON, unknown event type: the receiver responds in a controlled manner, and not with five hundred.

Checking repeats: the handler temporarily returns 500, after which the supplier log shows whether repeat deliveries actually arrived and at what intervals. This is the only way to find out the real retray policy, and not the one written in the documentation.

Response speed test: processing is artificially slowed down, but response time should remain the same. If it has grown, processing is performed inside the request, and this is fixed before launch, and not after the first surge in load.

The reconciliation starts according to a schedule and writes the result to the log even if there are zero discrepancies: a silent reconciliation is indistinguishable from a broken one.

The validity period of the certificate on the recipient domain is monitored along with other checks of the endpoint's availability from the outside.

  • webhooks
  • integration
  • API
  • reliability