Your agent did not react to a package, and there are several boundaries where the signal may have stopped. Start with the test event and move outward one boundary at a time. Guessing from the agent's silence cannot tell you whether RelayLink called, the receiver rejected, or the worker failed after returning success.
Send the account-page test first
Sign in and open /account/webhooks. The receiver must be active before the Send a test event button can queue webhook.test.
The page says the event is queued because pressing the button does not make a synchronous POST. Give the worker a few seconds, then refresh or revisit the page. Look for:
- A last-delivered time
- A last-attempt-failed time
- An HTTP status code, when a response arrived
- A transport error, when no response arrived
The test payload contains event, delivery_id, sent_at, and mcp_url. It deliberately has no package_id. If your handler requires package fields before it can return 2xx, the test will expose that mistake.
Check the HTTP response contract
RelayLink accepts any 2xx response. A 200, 202, or 204 can all acknowledge receipt. The response body is not used.
A redirect is not followed. A 30x therefore appears as a failed receiver status, even if a browser would have followed it to the intended route. Register the final HTTPS URL instead.
The default attempt timeout is five seconds. Verify, durably record the delivery id, enqueue work, and answer before then. Do not run the model or wait for get_package inside the callback. If the page says timed out while your logs show that processing eventually finished, your synchronous path is doing too much.
For a non-2xx response, inspect the receiver's logs for the same delivery id. Common causes are a route or method mismatch, a body-size rule, stale-timestamp policy, failed HMAC verification, or an application exception before the response was written.
Verify HTTPS and public reachability
The registered URL must use HTTPS, carry no URL username or password, and have no fragment. Its host must resolve to publicly routable addresses.
Registration rejects a host that resolves only to non-public addresses, but a name that does not resolve yet may still be saved. Every attempt resolves again and dials only a vetted public address. That connect-time check closes the DNS-rebinding gap. A receiver reachable only on a VPN, private subnet, loopback interface, or local development tunnel that is no longer running will fail.
Confirm public DNS from outside your network, a valid TLS certificate, and an ingress rule that permits POST requests. If a proxy sits in front, make sure it forwards the raw body unchanged and does not redirect the route.
Diagnose signature failures with the raw request
If the POST reaches the receiver but authentication fails, log safe diagnostics: delivery id, event, timestamp, body byte length, and the reason category. Never log the signing secret, raw body, whole signature, or full registered URL. The URL path may itself be a credential.
Recompute HMAC-SHA256 over the UTF-8 bytes of:
timestamp header text + "." + exact body text
Use the whsec_ secret shown at registration, lowercase hexadecimal after sha256=, and a constant-time MAC comparison. Do not verify a JSON object your framework serialized again. The signature guide isolates each byte-handling trap.
Understand retries and automatic disablement
By default, a failed delivery receives up to six attempts over about an hour. Each retry has the same delivery id and byte-identical body. Your receiver must deduplicate it durably.
Automatic disablement is a separate counter. After 30 consecutive failed attempt outcomes across deliveries, RelayLink switches the subscription off and abandons its queued deliveries. This does not mean one delivery receives 30 attempts. A successful attempt resets the consecutive-failure count.
Fix the receiver, return to /account/webhooks, and choose Turn back on. Re-enabling clears the failure count, but it does not replay deliveries already abandoned when the receiver was off. Send another test, and use check_inbox to reconcile packages that may have arrived during the outage.
When tests pass but package events do not
Confirm the receiver handles package.received separately from webhook.test, then inspect its queue and worker. A package callback is only an envelope; the worker must authenticate independently to mcp_url and call get_package.
A 2xx response does not mean that fetch happened. It only records accepted transport. Likewise, a blocked sender queues no webhook at all. Webhook queueing or receiver failure must not prevent the separate notification email, so an email arriving without agent activity points back toward this webhook and worker path rather than proving a package was read.