> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usepatchwork.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Relay service

> A backend that fronts Patchwork for a channel you own, and hosts the signed tools the agent calls back into.

The full-fidelity relay integration: your service drives conversations *and* serves the endpoints the agent calls. Needs a [connection](/guides/connections) pointing at your host, with a request secret.

## Driving the conversation

```js theme={null}
import express from "express";

const BASE = "https://api.usepatchwork.co/v1/loom";
const app = express();

function headers(subject) {
  return {
    Authorization: `Bearer ${process.env.PATCHWORK_API_KEY}`,
    "Patchwork-Subject": subject,
    "Patchwork-Connection": process.env.PATCHWORK_CONNECTION_ID,
    "Content-Type": "application/json",
  };
}

app.post("/chat", requireUser, async (req, res) => {
  // Subject derived from the authenticated session, never from the body.
  const subject = `${req.user.id}:${req.user.tenantId}`;
  const h = headers(subject);

  const threadId = await threadFor(req.user, h);
  const { data } = await fetch(`${BASE}/threads/${threadId}/messages`, {
    method: "POST", headers: h,
    body: JSON.stringify({ content: req.body.message }),
  }).then((r) => r.json());

  res.json({ run_id: data.run_id, thread_id: threadId });
});
```

## Receiving the calls back

When the agent uses one of your patches, we call your host. Verify the signature before doing anything — see [Signing and verification](/guides/signing).

```js theme={null}
import crypto from "crypto";
import jwt from "jsonwebtoken";

// Capture the raw body: signing is over bytes, not re-serialised JSON.
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf.toString(); } }));

function verifyPatchwork(req, res, next) {
  const header = req.get("Patchwork-Signature");
  if (!header) return res.status(401).end();

  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.status(401).end();

  const digest = crypto.createHash("sha256").update(req.rawBody ?? "").digest("base64");
  const expected = crypto
    .createHmac("sha256", process.env.PATCHWORK_REQUEST_SECRET)
    .update(`${t}.${req.method}.${req.path}.${digest}`)
    .digest("hex");

  const a = Buffer.from(expected), b = Buffer.from(v1 ?? "");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.status(401).end();

  // On a minted connection, the subject token rides alongside the signature.
  const bearer = req.get("Authorization")?.replace(/^Bearer /, "");
  if (bearer) {
    const claims = jwt.verify(bearer, process.env.YOUR_PUBLIC_KEY, {
      algorithms: ["RS256"], audience: "your-api",
    });
    req.subject = claims.sub;
  }
  next();
}

app.post("/tools/orders/:id/refund", verifyPatchwork, async (req, res) => {
  // Your normal authorization runs unchanged, against the subject.
  const [userId, tenantId] = req.subject.split(":");
  await authorizeRefund({ userId, tenantId, orderId: req.params.id });

  const refund = await refundOrder(req.params.id, req.body.amount);
  res.json({ reference: refund.reference });
});
```

The subject is the same string you asserted on the way in. Patchwork passed it through and never widened it — whatever that user could do before, they can do here, and nothing more.

## The health endpoint

```js theme={null}
app.post("/patchwork/up", verifyPatchwork, (req, res) => {
  const proof = crypto
    .createHmac("sha256", process.env.PATCHWORK_REQUEST_SECRET)
    .update(`patchwork-up.v1:${req.body.nonce}`)
    .digest("hex");
  res.json({ nonce: req.body.nonce, proof });
});
```

Pressing **Test** on the connection should now report `verified`. Anything less means the secret does not match.

## Terminal events by webhook

Rather than polling every run to completion, register a [webhook endpoint](/api-reference/webhook-endpoint) for `run.completed` and `run.failed`:

```js theme={null}
app.post("/patchwork/events", verifyWebhook, async (req, res) => {
  res.status(200).end();           // ack fast, work after
  const event = req.body;

  if (await seen(event.id)) return; // Patchwork-Delivery, deduplicated
  await recordDelivery(event.id);

  if (event.type === "run.completed") await notifyUser(event.run_id);
});
```

`verifyWebhook` is the same signature check keyed with the **endpoint's** secret rather than the workspace request secret.

Pause events are not webhooked. To react to a held write or a proposed action from a backend, poll `GET /v1/loom/threads` for the `pending_*` fields.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.