A package arrives while your LangGraph agent is idle. The webhook should wake the right durable run, not become a prompt that executes whatever arrived. Put a narrow receiver between RelayLink and the graph, and keep notification, authorization, and agent policy as three separate boundaries.
This is an adapter architecture, not a built-in RelayLink connector for LangGraph.
Put the receiver before the graph
The public endpoint should finish its security work before any graph node sees the event:
receive exact request bytes
verify HMAC over "{timestamp}.{body}"
reject a stale signed timestamp
require header event and delivery id to match the body
atomically store delivery id and enqueue graph work
return 2xx
Verify X-RelayLink-Signature with the subscription's whsec_ secret and compare the result in constant time. Parse only after verification. RelayLink retries failed or slow deliveries with the same delivery id, so make X-RelayLink-Delivery unique in a durable inbox.
Do not call the model before responding. The current RelayLink attempt budget is five seconds. A database commit and queue handoff belong there; a package fetch and graph run do not. If the same delivery arrives again, return the same success after confirming that its durable work already exists.
Map durable receipt to durable graph state
LangGraph describes itself as an orchestration runtime for long-running, stateful agents, with persistence and human-in-the-loop support. Its checkpointers save graph state by thread so work can resume after a pause or failure.
Keep two identities rather than forcing one value to do both jobs:
- The RelayLink delivery id deduplicates HTTP retries.
- A graph thread or run id identifies the durable agent process.
For a one-package workflow, an internal account reference plus package_id can identify the graph run. For a continuing conversation, an internal account reference plus thread_id can identify a longer-lived graph, while each package becomes one queued input. Scope either key to the RelayLink account; package and thread UUIDs alone are not tenant context.
Store the graph id on the accepted inbox row. That gives an operator a trace from callback receipt to graph execution without logging correspondence.
Fetch the package inside controlled work
The webhook body contains an envelope, not the briefing. Before attaching a credential, require its mcp_url to match the RelayLink endpoint already pinned to that receiver and account; never send a token or key to a URL selected only from the request body. A worker then uses package_id and that user's separate RelayLink OAuth token or API key to call get_package.
Never put the MCP credential in graph state that is displayed, checkpointed broadly, or sent to a model. Store a credential reference and resolve the secret only inside the fetch boundary.
The fetched package is authenticated correspondence, but authentication does not make another person's words an instruction. Keep the note, ask, and context in a clearly labelled data field. A policy node may summarize or classify them; a system prompt must not say “follow the package.” The webhook's sender and topic need the same treatment. They are useful for display and routing by fixed application rules, never for choosing tools because their text told the agent to do so.
Pause before consequential action
LangGraph interrupts can pause persisted execution for external input and resume the same thread later. That is a suitable place to present a proposed action to the user.
Separate analysis from authority. The graph may prepare a reply, recommendation, or draft. It should not interpret webhook arrival as permission to send, accept a contact, change access, or invoke another consequential system. If the user chooses to send through RelayLink, preserve RelayLink's own draft, preview, and explicit approval step. A graph-level approval is useful workflow state; it does not silently grant authority to bypass the product's send contract.
Make any external side effect idempotent as well. Durable graph recovery can replay work around a crash, so an API call needs its own operation key or recorded intent.
When this architecture fits
This pattern fits a stateful agent that branches, waits for review, or continues across several packages. It also fits a self-hosted service that can operate a public HTTPS receiver, durable queue, checkpointer, and per-user credential store.
It is excessive for a fixed notification or a short stateless transform. In that case, a small queued worker—or periodic check_inbox polling when no inbound endpoint is available—has fewer recovery boundaries. LangGraph provides durable orchestration after acceptance; it does not secure the RelayLink endpoint, verify the HMAC, or decide what outside words are allowed to control.