Patchwork-Signature header computed over your workspace request secret. If the connection uses minted egress, it also carries a short-lived token for the subject.
Two credentials, two questions:
They must never collapse into one, and neither substitutes for the other. This page covers the signature. For where it sits among the other secrets — which key points in which direction, and which ones you need — see The trust model.
The signature
v2. During a secret rotation it sends one v2 per live secret, so a header can carry several.
Each value is HMAC-SHA256 over your request secret.
v2
v2. in front. Two rules matter:
- The query is signed exactly as it arrived. No sorting and no re-encoding.
?b=2&a=1and?a=1&b=2are different signatures, and so are%20and+. Use the raw query string your server received, the way you use the raw body. - No query means no
?. If the request has no query string, the target is just the path.
v2. prefix is there because without it the two labels would cover identical bytes for a request with no query, and one signature could be replayed as the other.
v1, retired
An earlier label, v1, covered the same payload without the query string:
v1 signature did not cover them and a request whose arguments were changed in flight still verified.
You do not need to implement it. If you already accept it, you can leave that code in place — nothing emits a v1 value, and nobody can produce one without your request secret.
Unknown labels
Ignore a label you do not recognise rather than rejecting the header. That is what lets a new label be added without breaking you, and it is howv2 was introduced.
Verifying
- Use the raw body. Signing happens over bytes. Re-serialising parsed JSON changes whitespace and the digest will not match.
- Use the raw query. Same reason. A parsed-and-rebuilt query string will have been sorted or re-encoded by your framework, and the digest will not match.
- Compare in constant time.
===on an HMAC leaks timing. - Collect every value, not one. A header is a list, not a map. During a rotation Patchwork sends one value per live secret, so
t=…,v2=…,v2=…is normal. Parsing it into an object keeps only the last and the overlap silently stops verifying. - Verify against your current and previous secret. For 24 hours after you rotate, we sign with both, so a backend that has not yet deployed the new value keeps working. Accept if any value matches either secret.
- Check the length before a constant-time compare. Node’s
timingSafeEqualthrows when the buffers differ in length, so a short or truncated value becomes a 500 instead of a 401.
Then the subject token
On aminted connection the request also carries a JWT for the subject. Verify it with your own public key, read sub, and hand off to your normal authorization:
The health endpoint
Pressing Test on a connection posts a signed nonce to/patchwork/up. Echo it with a proof only computable with the shared secret:
ok will report reachable, not verified — it cannot fake the proof. Only verified means the trust is wired correctly.
Webhooks use the same scheme
Webhook deliveries are signed identically, but keyed with the webhook endpoint’s own secret rather than the workspace request secret. Same verification code, different key. A delivery goes to the URL you registered and carries no query string, so itsv2 target is just the path. During a secret rotation it carries one v2 per live secret, so a header can hold two values. Accept the request if any value matches any of your secrets.