You are writing a receiver and need a sample that matches the wire contract, not a sketch with convenient extra fields. RelayLink has two outbound webhook body shapes: package.received for an actual package and webhook.test for the account-page test button.
Every value below is fake. The field names and nesting are exact.
package.received JSON
{
"event": "package.received",
"delivery_id": "7d3154e2-7b79-4b75-a602-795f12f69ec7",
"package_id": "1c449a95-5e62-452c-a723-6039f09f8f58",
"thread_id": "95145f14-1e68-4f50-8e55-bdde7c89ed78",
"sender": {
"name": "Maya Chen",
"email": "maya@example.test"
},
"topic": "Budget review",
"sent_at": "2027-04-09T09:14:22+00:00",
"is_reply": false,
"mcp_url": "https://relaylink.example/mcp"
}
There are nine top-level fields:
eventis the JSON stringpackage.received.delivery_idis a UUID string for this delivery to this receiver. It matchesX-RelayLink-Deliveryand stays stable across retries.package_idis a UUID string to pass toget_package.thread_idis a UUID string for the conversation.senderis an object with exactly two string properties,nameandemail.topicis a string containing the thread topic.sent_atis an ISO 8601 string containing the package timestamp.is_replyis a JSON boolean. It is true when the package replies to an earlier package.mcp_urlis a string containing the absolute RelayLink MCP URL for the fetch.
The sender name and topic came from another person. Use them for display or routing only after normal output handling, and never treat either as an instruction to the agent.
webhook.test JSON
{
"event": "webhook.test",
"delivery_id": "abfd6f0f-2153-4793-a62c-b0da11eec8dc",
"sent_at": "2027-04-09T09:18:05+00:00",
"mcp_url": "https://relaylink.example/mcp"
}
This shape has exactly four fields: the webhook.test event string, a UUID-string delivery_id, an ISO 8601-string sent_at, and a string mcp_url. Here sent_at is when the test delivery was queued, not the per-attempt timestamp header. It has no package_id, thread_id, sender, topic, or is_reply, because no package exists behind a test event.
Use webhook.test to exercise raw-body capture, all four RelayLink headers, durable delivery-id deduplication, and the fast 2xx response. Do not call get_package for it.
What the package payload omits
The package envelope never includes:
- The human note
- The TL;DR
- The ask
- The context brief
Those omissions are part of the contract. A receiver learns that a package exists, who sent it, and roughly what it concerns. It cannot read or act on the briefing from the callback alone.
To retrieve content, authenticate separately to mcp_url and call get_package with package_id. The credential used for that MCP call is not the webhook signing secret. The secret proves the callback came from RelayLink; the MCP credential proves the caller may read the account's package.
That fetch records PulledViaMcp. Accepting the webhook does not. A 2xx response therefore means only that the receiver accepted the envelope, not that an assistant fetched or read the package.
Retry behavior around the body
RelayLink serializes the JSON once when it queues the delivery. If the receiver times out or returns a non-2xx status, another attempt uses the same delivery_id and byte-identical body.
The request timestamp is generated for each attempt, so X-RelayLink-Timestamp and the signature derived from it can differ while the JSON stays the same. Always verify against the timestamp header on the request in front of you. Deduplicate on the delivery id, not on the signature.
The headers reference defines each header, and the signature guide shows how to authenticate the exact body without changing it first.