> ## 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.

# The trust model

> Four secrets, two directions. Which key proves what, and which one you need for what you are building.

Patchwork and your backend call each other, so each side needs a way to prove it is itself. That is four secrets in total. They are easy to confuse because two of them sign HTTP requests and two of them end up in an `Authorization` header — so start from the direction each one points.

## The four secrets

| Secret | Looks like | Who holds it | Who checks it | It proves |
| - | - | - | - | - |
| **Workspace API key secret** | `sk_…` | You, server-side only | Patchwork | A call to Patchwork came from your backend |
| **Your private signing key** | An RSA or EC private key | You alone — it never leaves your servers | Patchwork, with the public half | A [session token](/guides/session-tokens) was minted by you, for one subject |
| **Workspace request secret** | `pwrs_…` | You *and* Patchwork | You | A call to your backend came from Patchwork |
| **Webhook endpoint secret** | `pwwh_…` | You *and* Patchwork | You | A [webhook delivery](/guides/events#verifying-a-delivery) came from Patchwork |

There is also an API key **public id** (`key_…`). It is not a secret. It goes in a session token's `iss` claim to name your workspace, and it authorizes nothing on its own.

## The two directions

```
inbound — you call us
  your backend  ── Bearer sk_… ──────────────────▶  Patchwork
  your browser  ── Bearer <session token> ───────▶  Patchwork

outbound — we call you
  Patchwork  ── Patchwork-Signature (pwrs_…) ────▶  your backend
  Patchwork  ── Patchwork-Signature (pwwh_…) ────▶  your webhook URL
```

Nothing crosses the middle. A secret that works inbound is never used outbound, and rotating one has no effect on the other.

## Inbound: how we know it is you

The form of the bearer token selects the mode. You do not declare one.

**Relay** sends the workspace API key. The secret *is* the proof — we hold a hash of it, compare, and resolve the workspace. Because that key can assert any subject in your workspace through the `Patchwork-Subject` header, it must stay server-side.

**Direct** sends a session token your backend signed. Here the proof is a signature, not a secret:

1. `iss` names an API key public id, which selects your workspace and the key to check against. This is routing only.
2. The signature must verify against the public key you registered — your JWKS document, or a key pasted in the dashboard.
3. The algorithm must be asymmetric. `HS*` is refused, always. Accepting a symmetric algorithm while holding your *public* key would let anyone sign a token with a value we publish.
4. `aud` must include `patchwork`, and `exp` must still be in the future.

A token that fails any of these is rejected outright. It is never downgraded to relay.

<Note>
  You do not sign your requests to us. The bearer token is the whole credential on the inbound leg, which is why `sk_…` stays on your server and a session token's `exp` should sit around 120 seconds.
</Note>

## Outbound: how you know it is us

Every request we make to something you host carries a `Patchwork-Signature` header — an HMAC over a shared secret, covering the method, path, query and body. Tool calls, the relay [mint](/guides/mint-endpoint) call, and the [health probe](/guides/signing#the-health-endpoint) are keyed with the workspace request secret. Webhook deliveries are keyed with that endpoint's own secret. The verification code is identical; only the key differs.

[Signing and verification](/guides/signing) has the payload format, the rotation rules, and the six details that break implementations.

## Why one is shared and one is not

A shared secret is the right tool for "is this Patchwork?" We are the only other party who could produce the value, so holding the same bytes proves nothing more than the question asks.

It is the wrong tool for "who is this user?" If we held a secret that could mint a token for one of your users, then a breach of us would be a breach of every account you have. So identity runs the other way: you keep the private key and we store only the public half. We can verify a subject. We cannot invent one.

What we actually store for your workspace is the request secret and its predecessor, your public verification key or JWKS URL, and a hash of each API key secret. No private key of yours is in it.

## What you need, by what you are building

| What you are building | What you need |
| - | - |
| Relay, and no agent calls your backend | The API key secret, and nothing else. We never call you |
| Relay, and an agent calls your backend | The API key secret, the request secret, and a [mint endpoint](/guides/mint-endpoint) |
| Direct | A keypair and a published JWKS, plus the request secret once an agent calls your backend |
| Webhooks | One endpoint secret, independent of everything above |

The last two rows are the ones people get wrong. A signing key is not enough for direct integration — the moment an agent calls an endpoint you host, you also verify signatures.

## Easy to get wrong

* **The request secret is not your signing key.** The request secret is symmetric and shared, and you use it to verify calls from us. Your signing key is asymmetric and private, and you use it to sign tokens. You will not find a private key anywhere in the dashboard — we never hold one.
* **`key_…` is not `sk_…`.** The public id belongs in a token's `iss`. The secret belongs in an `Authorization` header on your own server, and never inside a token.
* **A session token is not a signature.** On a minted connection an egress request carries both, because they answer different questions: the signature says the request came from Patchwork, the token says which of your users it is acting for. Verify both. Neither substitutes for the other.
* **A signature header is a list, not a map.** During a rotation it carries one value per live secret. Accept the request if any value matches any secret you hold.

## Rotation

Every rotation keeps the previous value working for a bounded window, so you can deploy the new one without an atomic switchover. Nothing in flight fails, and the old value stops being accepted when the window closes.

| Secret | Rotate by | Old value stops working |
| - | - | - |
| API key secret | Issuing a new key, moving your servers to it, revoking the old one | The moment you revoke |
| Signing key | Adding a new key to your JWKS, then switching `kid` | When you remove it from your JWKS. Never remove it before you switch |
| Request secret | The dashboard | 24 hours after you rotate, or at once if you revoke |
| Webhook secret | The dashboard | 24 hours after you rotate |

During a request-secret or webhook-secret overlap we sign with **every live secret** and send one `v2` value per secret, so the header carries two. That is what lets either side be ahead of the other: a backend still verifying against the old value keeps working, and one that has deployed the new value works too. Accept the request if any value matches any secret you hold — which is why [parsing the header as a list](/guides/signing#verifying) is not optional.

### If a secret leaked

Do not wait out the window. Rotate, deploy the new value, then **Revoke previous** — the old secret stops being accepted immediately.


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