Your SaaS product has one webhook service, but dozens or thousands of customers connect their own RelayLink accounts. A single handler can receive all of them. The hard part is deciding which customer's secret and MCP credential apply before trusting the request.
The webhook body will not answer that question. A RelayLink package envelope deliberately contains no recipient id or account id.
Give every account a distinct registered route
When a customer enables the integration, create an opaque receiver identifier and show them a URL such as:
https://hooks.example.com/relaylink/r_8M9p2K4xQ7
Store a mapping from that route to your internal context:
receiver route
-> SaaS tenant and user
-> RelayLink subscription secret
-> RelayLink fetch credential reference
-> delivery-id store
The customer signs in to their own RelayLink account, registers that exact URL in the portal, and receives an independent whsec_ secret. Capture the secret through your integration settings and store it as a credential, not as ordinary profile data.
Each RelayLink account performs its own registration. A central SaaS operator cannot create subscriptions for customer accounts through /mcp, because webhook management is portal-only.
A unique hostname can provide the same context as a unique path, but a path is often simpler. Do not put an email address, RelayLink user id, or customer name in it. Use an opaque value that can be replaced without renaming the customer.
Select context before verifying the body
On each POST:
- Match the request path to one active receiver record.
- Load that record's single expected subscription secret.
- Read the raw body bytes.
- Verify
X-RelayLink-Signatureover"{timestamp}.{body}"with a constant-time MAC comparison. - Check the signed timestamp for freshness.
- Parse the authenticated body and require its event and delivery id to match the headers.
- Atomically claim the delivery id and queue durable work with the already selected tenant and user context.
- Return 2xx after the handoff is durable.
Do not loop through customer secrets until one happens to verify. That turns every request into a secret scan, makes timing harder to reason about, and removes the clear relationship between the registered endpoint and the account that authorized it.
The route chooses the candidate context. The HMAC proves the request belongs to that subscription. Both must agree.
Never route by sender
The package envelope includes a sender name and email address. Those fields describe who wrote the package; they do not identify which of your customers received it.
One sender can write to several RelayLink accounts. Several people can also use the same display name. Routing by sender can therefore deliver a notification into the wrong customer's workflow even when the webhook itself is genuine.
Sender and topic are also correspondence fields supplied from outside your system. Treat them as display data after authentication, never as a tenant lookup, secret selector, log template, or instruction.
The absence of a recipient id is intentional. The destination URL is the routing context the account registered. Your receiver should preserve that context rather than reconstructing it from message data.
Fetch with the selected account's credential
A valid webhook does not authorize package access. It contains package and thread ids plus mcp_url, but not the briefing.
The queued worker must use the separate OAuth token or named API key associated with the account selected by the route. Never borrow another tenant's credential because the package id looks familiar. RelayLink authentication and ownership checks should agree with your own endpoint mapping; a refusal is a routing incident to investigate, not a reason to try other credentials.
Keep signing secrets and fetch credentials in separate fields or secret-store entries. Rotating or revoking one has a different meaning from changing the other.
Returning 2xx records only that the receiver accepted the envelope. The separate get_package call is what records an authenticated package pull; webhook acceptance is not a read.
Treat endpoint paths as sensitive, but not sufficient
Webhook paths routinely contain tokens. RelayLink avoids exposing them in its own operational surfaces: logs identify the subscription, and account notices name only the URL's host.
Apply the same hygiene. Redact the path from access logs, traces, exception messages, analytics, and support exports. If the ingress platform cannot redact it, use a route identifier that grants no power by itself and keep retention short.
Even a well-protected path is not webhook authentication. Verify the HMAC, timestamp, and delivery id on every request. That layered design lets one SaaS receiver serve many RelayLink accounts without guessing who the webhook was for.