Reliable Webhook Ingress for AI Agents
Authenticate each webhook, use the provider’s event identifier as a unique deduplication key, persist the accepted payload, and hand it to a durable queue before returning a success response. Run the agent only from the queue consumer, where receiver-controlled retries and idempotent processing can absorb duplicate delivery and slow model calls.
Prerequisites
Before exposing the endpoint, you need three things: the sender’s documented authentication method and event identifier; durable storage that can enforce uniqueness; and a durable queue with a separate consumer. If an agent run can outlive one queue attempt, decide how it will use durable execution for a long-running workflow before accepting production traffic.
Know the sender’s response deadline. GitHub’s webhook best-practices documentation, checked 2026-08-26, requires a 2XX response within 10 seconds, recommends asynchronous queue processing, and says redelivery retains the original X-GitHub-Delivery value. Other senders can define different authentication fields, identifiers, and deadlines, so their contracts must be checked separately.
Build the ingress path
-
Define the smallest event contract you will accept.
Record which event types and actions may start an agent, where the sender places its event identifier, and which authentication material the endpoint needs. Reject an unsupported type or action before it reaches the queue. Subscribing only to events the agent can act on reduces stored noise and prevents an event added later by the provider from silently becoming a new trigger.
The cost is maintaining this allowlist as the integration changes. Accepting every event and filtering inside the agent avoids that maintenance, but it spends queue capacity and model work on requests that should have been rejected at ingress. It is the wrong choice when event types have different permissions or consequences.
-
Authenticate before acknowledging or persisting the event.
Read the request in the form required by the sender’s verification procedure, verify its secret or signature, and stop on failure. Do not create a queue message or agent run for an unauthenticated request. Keep the verification secret outside the payload URL and in the service’s secret store; the broader credential boundary is covered in where an agent should store issued credentials.
Authentication belongs on the short ingress path because the queue consumer should be able to treat its input as already admitted. Moving verification into the consumer lets the endpoint return success for a request that may later prove forged, while also filling durable storage with untrusted payloads.
This adds cryptographic verification to every delivery and makes secret rotation an operational concern. Skipping it is acceptable only when another trusted component has authenticated the request and the endpoint cannot be reached around that component.
-
Use the provider’s event identifier as the deduplication key.
The event identifier is the deduplication key because webhook providers commonly deliver at least once. A timeout, lost response, or requested redelivery can produce another request for the same event. Generate a separate identifier for the agent run if useful, but do not substitute it for the sender’s stable event identifier: a fresh run identifier makes every redelivery look new.
Enforce uniqueness in durable storage on a composite key such as
(provider, event_id). The provider prefix prevents unrelated senders from colliding. On a duplicate, load the existing record rather than inserting another or starting another run. If the same key arrives with different content, preserve the original instead of overwriting evidence needed to investigate the mismatch.There can be a second duplicate boundary after ingress. Cloudflare Queues’ delivery-guarantees documentation, last updated 2026-04-21, says its default is at-least-once delivery and recommends a unique identifier as a database key or downstream idempotency key. The unique index costs a write and retained state, but without it duplicate agent runs can race before application-level checks notice each other.
-
Persist the authenticated event before starting any agent run.
Write the event identifier, authenticated payload, event type, receipt time, and processing state to a durable inbox. Commit that record before any model call or tool call begins. A webhook endpoint should authenticate and persist the event before starting an agent run so a slow model call never holds the sender connection open.
Persistence defines the handoff boundary. Before the commit, failure means the receiver has not accepted responsibility and must not send success. After the commit, the receiver has enough information to recover processing without asking the sender to reconstruct the event.
If the database write and queue publication cannot share one transaction, store an outbox entry beside the inbox record in the same commit, then have a relay publish it. The alternative is publishing directly after the inbox commit and repairing records left unqueued by a crash. The outbox costs another record and relay, but closes that crash window without pretending two independent systems commit atomically.
-
Move processing to the durable queue, then return success.
Publish the durable event reference, or commit its outbox entry, and return the success status permitted by the sender.
202 Acceptedis a clear choice when the sender allows it: RFC 9110, published June 2022, defines202as accepted for processing but not yet completed, specifically so the connection need not remain open for asynchronous work. Use another2XXonly when the sender’s contract calls for it.Responding quickly with a success status shifts processing to a durable queue and lets retries follow the receiver’s policy. It does not claim that the agent completed its work. Returning success before durable persistence is the wrong boundary: a process crash can then lose an event the sender believes was accepted. Waiting for the agent is also wrong because model and tool latency becomes webhook response latency.
-
Make the queue consumer idempotent before it starts the agent.
The consumer should load the inbox row by event identifier and check its state. A completed event is acknowledged without another run. An unprocessed event may be claimed for work; concurrent deliveries must not both acquire that claim. Keep the event identifier attached to model calls, tool calls, logs, and any separate run identifier so correlation identifiers across agent runs preserve the path back to the original delivery.
Marking the inbox complete prevents a later queue redelivery from repeating the entire run. It cannot by itself undo a side effect performed just before a worker crashed. Pass the event identifier as an idempotency key to downstream tools that support one, or record each consequential operation before retrying it. This requires more state than a fire-and-forget consumer, but it is necessary when duplicate emails, writes, or external actions would be harmful.
-
Put retry decisions in the consumer, not the HTTP request.
Retry transient agent or tool failures through the queue. Set an attempt limit and a terminal state for events that need inspection; do not loop indefinitely inside the webhook request. A failure before the durable handoff receives no success response, leaving the sender free to redeliver under its policy. A failure after handoff stays inside the receiver, where queue retries can use the receiver’s policy without reopening the sender connection.
This split gives up immediate knowledge of the final outcome at the webhook call site. That is intentional: the HTTP response reports acceptance, while the inbox reports processing state. If the sender requires a synchronous business result rather than an acceptance receipt, a webhook-triggered asynchronous agent is the wrong interface.
-
Test each failure boundary with the same event identifier.
Send an invalidly authenticated request and confirm that nothing is persisted. Send one valid event and confirm that the inbox commit and queue handoff happen before the success response. Deliver that event again and confirm that the unique key prevents a second run. Interrupt processing after persistence and verify that queue delivery resumes from the stored event. Finally, delay the model call beyond the sender’s response deadline and confirm that the webhook response is unaffected because the model runs only in the consumer.
Expected result
Done means every accepted webhook is authenticated and durably recorded before success is returned; repeated deliveries resolve to one event record; the agent never runs on the sender’s connection; and processing failures are retried or stopped according to the receiver’s queue policy.
Sources
- GitHub’s webhook best-practices documentationdocs.github.com
- Cloudflare Queues’ delivery-guarantees documentationdevelopers.cloudflare.com
- RFC 9110rfc-editor.org
See also
A five-step procedure for handling a secret an agent was just issued: local env file, verified ignore rule, no client bundle, no reliance on redaction.
Bind agent outputs to their bytes, record how each was produced, and verify the digest, builder, and inputs against policy before use.
How to bound agent work at admission, track queue age, choose overflow behavior, preserve fairness, and return useful overload responses.
Separate caller cancellation from deadlines, propagate both, and reserve part of every agent run budget for cleanup and reporting.