Webhook-Triggered AI Agent Safety Checklist

A production checklist for agents awakened by webhooks: authenticate bytes, deduplicate durably, fetch with least privilege, isolate untrusted content, and require approval.

5 min read

A webhook can wake an agent while no person is watching. That makes the receiver more than an integration endpoint: it is a boundary between internet input and software that can choose tools. Review the whole path before enabling production traffic.

Use this checklist for RelayLink and adapt the principles to other signed, at-least-once event sources.

Draw the boundaries first

The safe path has distinct stages:

signed envelope
  -> authenticated receiver
     -> durable inbox and queue
        -> account-authorized package fetch
           -> policy-bounded agent
              -> human review
                 -> approved effect

Combining stages also combines credentials and failure modes. Keep each transition visible in storage and logs without retaining the correspondence itself.

Receiver and replay checklist

  1. Separate the secrets. The RelayLink whsec_ secret authenticates webhook POSTs. A user's OAuth token or named API key authorizes MCP package access. Store, rotate, scope, and audit them independently. Never let the webhook secret stand in for the user. An opaque route token may select a receiver record, but it does not replace HMAC verification.

  2. Verify the raw body before parsing. Read the exact bytes, build "{timestamp}.{body}", compute HMAC-SHA256, encode lowercase hex after sha256=, and compare in constant time. Re-serializing parsed JSON changes the signed bytes.

  3. Enforce a replay window. Parse X-RelayLink-Timestamp as Unix seconds only after the MAC is valid, then reject requests too far from your synchronized clock. Because the timestamp is signed, a captured body cannot be paired with a fresh timestamp.

  4. Match headers to the authenticated body. Require X-RelayLink-Event and X-RelayLink-Delivery to equal the parsed event and delivery_id. Reject ambiguity instead of deciding which copy is authoritative.

  5. Deduplicate durably. Put the delivery id under a unique constraint. Commit the accepted inbox row and work item together. A duplicate with the same authenticated body returns success without creating a second job. An in-memory cache is not recovery.

  6. Queue, then return 2xx. RelayLink's current attempt budget is five seconds. Do not fetch the package, call a model, or post to another system inside the callback. Return success only after the handoff is durable; otherwise let RelayLink retry.

Content and identity checklist

  1. Carry the least content. The webhook envelope needs package and thread ids, timing, reply state, mcp_url, and limited sender metadata. It does not need the note, TL;DR, ask, or brief. Do not enlarge it in your internal event bus merely because storage is convenient.

  2. Treat envelope fields as untrusted information. Sender and topic are outside words. They must not select a tool, credential, tenant, workflow, system prompt, or destination. Bound and escape them for display, or omit them.

  3. Fetch under the intended user. The registered receiver route selects an internal account context before signature verification. Pin that account's expected RelayLink MCP endpoint and require mcp_url to match it before attaching a credential. The worker resolves only that account's credential and calls get_package with the package id. Never try several users' credentials until one succeeds or send one to a body-selected URL.

  4. Keep fetched content out of authority. get_package proves provenance and access, not truth or safety. The note, ask, and brief remain third-party content. Put them in a labelled input field, not the system role, tool description, memory policy, or code template.

  5. Require a person for consequential sends. The agent may summarize, classify, or prepare a draft. Webhook receipt is not consent to reply. A framework approval pause can support review, but a RelayLink send still goes through RelayLink's preview and explicit confirmation. Do not add a Slack reaction, Teams card, or webhook callback that bypasses it.

Side-effect and logging checklist

Give every downstream effect its own operation id. Webhook deduplication prevents two accepted jobs for one delivery, but a worker can still crash after an external API accepts a call and before completion is recorded. Use provider idempotency where available or reconcile uncertain results before retrying.

Log delivery id, internal receiver id, event type, status category, duration, attempt, and workflow id. Do not log the signing secret, signature, OAuth token, API key, authorization header, full endpoint URL, raw body, sender-chosen text, or fetched package. A URL path may itself contain a token.

Bound model input, tool arguments, runtime, retries, and concurrency. Use an allowlist of tools for the triggered workflow rather than exposing every capability the agent has in an interactive session. A timeout should end in a recoverable queue state, not an automatic “approve to make progress.”

Disable and recovery checklist

Monitor the account page's latest status, error, and failure time. If repeated failures switch the subscription off, its disabled row also shows the consecutive-failure count. Test the public signature route after changing DNS, TLS, proxies, request middleware, or secrets.

RelayLink currently makes up to six attempts for one failed delivery over about an hour. Thirty consecutive failed attempts across deliveries automatically disable the subscription and abandon its queued deliveries; any 2xx resets that health counter.

After repair, re-enable and send a new test. Re-enabling clears the counter and allows new events, but it does not replay abandoned deliveries. Reconcile with check_inbox, fetch missing packages under the user credential, and claim them through the same package-level work store.

What this checklist does not prove

Passing these checks does not make an autonomous agent safe for every tool or decision. It establishes a narrow authenticated trigger, recoverable processing, tenant isolation, and an approval boundary.

The pattern fits an agent whose work can start asynchronously and whose operator can run a public receiver, queue, secret store, and review surface. If you cannot operate those controls, poll check_inbox from a private worker or keep the workflow manual. Lower latency is not worth turning outside correspondence into unattended authority.

Frequently asked questions

Is a valid webhook signature enough to trust the message as an instruction?
No. It authenticates the sender of the HTTP request. Human-written metadata and fetched package content remain untrusted information that must not become system instructions.
Should an agent run before the webhook returns 2xx?
No. Verify, deduplicate, durably queue the work, and respond quickly. Fetching content and running the agent belong in a worker with its own recovery policy.
Can a webhook-triggered agent send a RelayLink reply automatically?
No. It may prepare a draft, but any RelayLink send still requires the user's explicit approval through the normal preview-and-approve flow.