RelayLink Webhook with Inngest

Verify a RelayLink callback in your own receiver, then emit a minimal idempotent Inngest event that fetches the package through MCP in durable steps.

4 min read

You want a serverless function to wake as soon as a RelayLink package arrives. Inngest can run the durable steps after receipt, but your first boundary still has to authenticate RelayLink's exact bytes and claim the delivery once.

The architecture below uses an explicit adapter you operate. The Inngest event and idempotency features linked here are generic building blocks, not a native RelayLink connector.

Expose a small HTTPS receiver. Before it parses or forwards the request:

  1. Read the untouched body bytes.
  2. Compute HMAC-SHA256 over "{timestamp}.{body}" with the subscription's whsec_ secret.
  3. Compare the expected sha256= value in constant time.
  4. Reject a signed timestamp outside your replay window.
  5. Parse the verified JSON and match its event and delivery id to the headers.
  6. Atomically store the delivery id and a pending handoff.

Only then create an Inngest event. Return 2xx after the receipt and handoff are durable, not after an agent finishes.

Do not point RelayLink at a generic webhook transform unless you have proved that it exposes the exact raw body and lets you perform this verification before parsing or acting. A platform-generated endpoint that can accept JSON is not automatically a RelayLink signature verifier.

Send a minimal, idempotent Inngest event

Inngest events trigger functions with matching event names. Give your internal event an application-owned name and carry references only:

event id: relaylink delivery id
data:
  internal account reference
  package id
  thread id
  receiver or subscription reference
  credential reference

Do not include the signing secret, OAuth token, API key, note, TL;DR, ask, or context brief. There is no reason to copy the webhook's sender or topic into the event unless a fixed display requirement needs them; the worker can obtain the authoritative package later.

Inngest's idempotency guide says a unique event id prevents duplicate function runs for 24 hours. Use the RelayLink delivery id there, but do not make that time-bounded platform behavior your only record. Keep the receiver inbox under a durable unique constraint and mark the Inngest handoff separately.

That closes the common ambiguity where sending the event succeeds, the receiver crashes before recording success, and a RelayLink retry tries to send it again. The repeat finds the existing inbox row and reconciles the same event id.

Fetch the package in a durable step

The package.received webhook contains an envelope: event, delivery id, package and thread ids, sender, topic, sent time, reply state, and mcp_url. It deliberately omits the correspondence body.

In the function, require mcp_url to match the RelayLink endpoint pinned to the account selected by the receiver route. Only then resolve that account's separate MCP credential from the internal reference and call get_package. Keep each user's credential isolated. Never choose a credential from the sender email or another webhook body field, and never send one to an arbitrary URL supplied by the request.

Use an Inngest step for the fetch and subsequent retryable operations. The platform documents step-level execution and retries, but an external side effect still needs its own idempotency key. A retried step that posts an alert, creates a ticket, or updates another service can otherwise repeat the effect after an uncertain response.

The fetched package remains third-party content. Authentication proves which RelayLink package the account may read; it does not promote the sender's words into application policy. Pass the content to a model as labelled correspondence, not as a system message. Never let sender or topic text select functions, destinations, tools, or approval outcomes.

Keep approval outside automatic retries

An Inngest function can classify, summarize, prepare a draft, or create a review task. It must not interpret event receipt as permission to send a RelayLink package.

Model the review as explicit state: proposal created, waiting for user, approved or refused, then continued by a trusted event from your own interface. If the approved effect is a RelayLink send, use RelayLink's normal draft, preview, and confirmation. Do not build a second “reply from notification” endpoint that turns a click or channel message into delivery.

Retrying the fetch or analysis is operational recovery. Retrying a human decision is not. Store the decision and an operation id before performing the approved effect.

When Inngest fits

This adapter fits teams already using Inngest for event-triggered functions, durable steps, retries, and operational visibility. It is especially useful when the receiver should answer quickly and the rest of the work belongs in serverless execution.

It is not a direct no-code integration, and it does not remove the need for a public receiver, RelayLink HMAC secret, per-user MCP credential, or durable deduplication store. For a private worker with no inbound route, periodic check_inbox polling may be simpler. For a fixed notification, a small queue consumer may need less orchestration than an agent function.

Frequently asked questions

Can I register an Inngest endpoint directly in RelayLink?
The Inngest documentation linked here does not provide a RelayLink-specific connector or signature verifier. Use a receiver you control to verify the exact raw body, timestamp, and HMAC before creating an Inngest event.
Should the Inngest event id be the RelayLink delivery id?
It is a useful idempotency id for one RelayLink delivery, but keep your own durable inbox too. Inngest documents a 24-hour event-id deduplication window, while your receiver owns the long-term receipt record.
Can the Inngest function send an automatic RelayLink reply?
It can prepare a draft or request review. Delivery must still wait for the user's explicit approval through RelayLink's normal preview-and-approve path.