> For the complete documentation index, see [llms.txt](https://docs.idlayr.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.idlayr.com/get-started/signed-http-messages.md).

# Signed HTTP messages

IDlayr signs **two kinds of outbound HTTP messages**:

* **Callbacks** — POSTs to your callback URLs when a verification reaches a terminal state.
* **Redirect URLs** — the verification URLs the SDK follows during a Secure SNA or Discovery flow.

Both use the same signing primitive — RSA-SHA256 over a defined payload — but they carry the signature in different places: callbacks in an `Authorization` header, redirects in URL query parameters. Verifying the signature is **mandatory** before trusting either payload. An unsigned or wrongly-signed request is not from IDlayr.

## Signing primitives

|                 |                                                                 |
| --------------- | --------------------------------------------------------------- |
| Algorithm       | RSA-SHA256                                                      |
| Specification   | HTTP Signatures (Cavage draft)                                  |
| Key publication | JWKS (JSON Web Key Set)                                         |
| JWKS endpoint   | `https://{data_residency}.api.idlayr.com/.well-known/jwks.json` |

The JWKS endpoint returns a `keys` array. Each entry has a `kid` (key identifier) and an `alg` (algorithm — always `RS256` today). Multiple keys may be present at once during a rotation; match the `keyId` from the signature against the `kid` field on a JWK to pick the right key.

## Callbacks: signing in the `Authorization` header

Callbacks arrive as HTTPS POSTs with a JSON body. The signature is in the `Authorization` header in the `Signature` scheme:

```http
POST /your/callback HTTP/1.1
Host: your.app
Date: Tue, 16 Jun 2026 14:00:00 GMT
x-idlayr-callback: secure_sna
Digest: SHA-256=<base64 of SHA-256(body)>
Authorization: Signature keyId="<kid>",algorithm="rsa-sha256",headers="(request-target) host date x-idlayr-callback digest",signature="<base64 signature>"
Content-Type: application/json

{ "check_id": "...", "status": "COMPLETED", ... }
```

The signed payload is the concatenation of the headers listed in the `headers` attribute, in that exact order, separated by `\n`. For the example above:

```
(request-target): post /your/callback
host: your.app
date: Tue, 16 Jun 2026 14:00:00 GMT
x-idlayr-callback: secure_sna
digest: SHA-256=<base64 of SHA-256(body)>
```

The `(request-target)` pseudo-header is the lowercased HTTP method, a space, and the request path. The `digest` header carries a SHA-256 hash of the **raw request body** — this binds the body to the signature, so re-computing the digest is part of verification.

### Verifying a callback (Node.js)

```javascript
const httpSignature = require('http-signature');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
  jwksUri: `https://${region}.api.idlayr.com/.well-known/jwks.json`,
  cache: true,
});

async function verifyCallback(req) {
  // 1. Parse the Signature header.
  const parsed = httpSignature.parseRequest(req);

  // 2. Look up the public key by keyId.
  const key = await client.getSigningKey(parsed.keyId);
  const publicKey = key.getPublicKey();

  // 3. Verify the SHA-256 digest matches the body.
  const expectedDigest = 'SHA-256=' + crypto
    .createHash('sha256')
    .update(req.rawBody)
    .digest('base64');
  if (req.headers.digest !== expectedDigest) {
    throw new Error('digest mismatch');
  }

  // 4. Verify the signature.
  if (!httpSignature.verifySignature(parsed, publicKey)) {
    throw new Error('signature invalid');
  }
}
```

Note that the verification needs access to the **raw, byte-exact request body** — not the parsed-then-re-serialised body. Any framework middleware that re-serialises the body will change byte-level whitespace and break the digest check.

## Redirect URLs: signing in query parameters

During Secure SNA and Discovery flows, the SDK follows a redirect chain. The final redirect lands on your `redirect_url` (for browser-based flows) carrying the verification code, `check_id`, and a **signature in the URL itself**.

Example redirect:

```
https://your.app/sna-return
  ?check_id=8c7b...
  &code=NjQyN2I...
  &date=VHVlLCAxNiBKdW4gMjAyNiAxNDowMDowMCBHTVQ%3D
  &authorization=Signature%20keyId%3D%22...%22%2Calgorithm%3D%22rsa-sha256%22%2C...
```

The `authorization` query parameter carries the same `Signature` scheme as the callback header, URL-encoded. The `date` query parameter carries the timestamp, base64-encoded. Together they let your `redirect_url` handler verify that the redirect came from IDlayr rather than from a tampered client-side chain.

### Verifying a redirect (Node.js)

```javascript
const url = new URL(req.url, `https://${req.headers.host}`);
const authorization = url.searchParams.get('authorization');
const date = Buffer.from(url.searchParams.get('date'), 'base64').toString();

// Build a synthetic request object that http-signature can parse.
const synthetic = {
  method: 'GET',
  url: url.pathname,
  headers: {
    host: req.headers.host,
    date,
    authorization,
  },
};

const parsed = httpSignature.parseRequest(synthetic);
const key = await client.getSigningKey(parsed.keyId);
if (!httpSignature.verifySignature(parsed, key.getPublicKey())) {
  throw new Error('signature invalid');
}
```

The query-parameter format is what makes redirect signing work end-to-end: the SDK can't be trusted to forward HTTP headers across the redirect chain, but query parameters are preserved by every intermediary.

## Verification, step by step

Regardless of whether you're verifying a callback or a redirect, the model is the same:

1. **Parse** the `Signature` line to extract `keyId`, `algorithm`, the ordered `headers` list, and the `signature` value.
2. **Fetch the JWKS** (cache aggressively — keys don't change often), and pick the JWK whose `kid` matches the parsed `keyId`. Reject if no JWK matches.
3. **Reconstruct the signing payload** — concatenate the listed headers (or their query-parameter equivalents for redirects), in the listed order, joined by `\n`. Each line is `header-name: value` for headers; the `(request-target)` pseudo-header is `method path`.
4. **Convert the JWK to PEM** so your crypto library can use it. Most languages have a helper for this.
5. **Verify** using RSA-SHA256 with the public key against the reconstructed payload.
6. **For callbacks**, also verify the `Digest` header matches a fresh SHA-256 of the raw body. The signature only binds to the digest, not the body itself — without the digest check, an attacker who replays the headers can swap the body.
7. **Reject** on any failure. Don't act on the payload.

## Key rotation

IDlayr rotates signing keys periodically. During a rotation, the JWKS will contain **multiple keys** — both the outgoing key and the new key. Your receiver should:

* **Always look up by `keyId`** — don't pin to a specific key in code.
* **Refresh the JWKS** on a sensible cadence. Hourly is plenty; on `keyId` miss, force-refresh once and try again before failing.
* **Don't cache forever**. A key that's been retired won't appear in the JWKS after rotation completes.

## Common errors and how to debug

| Symptom                                        | Likely cause                                                                                                                                                                                                    |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `signature invalid` despite the keyId matching | The signing payload was reconstructed with the wrong header order, a wrong line separator (`\r\n` instead of `\n`), or a normalised vs raw header value. Check exactly which bytes you're signing.              |
| `digest mismatch`                              | Your framework re-serialised the JSON body before you computed the digest. Capture the raw bytes — most frameworks have a "raw body" hook for exactly this purpose.                                             |
| JWK not found for `keyId`                      | Either you're hitting the wrong data-residency JWKS endpoint (EU vs US — match the region of the calling project), or your JWKS cache is stale. Force-refresh.                                                  |
| Clock-skew complaints                          | The `date` header is informational, not signed-into-the-clock — IDlayr doesn't reject your callback for a stale clock, but downstream replay protection might. If you're enforcing freshness, allow ±5 minutes. |

## Reference implementations

Pseudocode and Node.js samples in this page use the [`http-signature`](https://www.npmjs.com/package/http-signature) library plus [`jwks-rsa`](https://www.npmjs.com/package/jwks-rsa). Equivalent libraries exist for most ecosystems — search "HTTP Signatures Cavage" plus your language. The protocol is stable; library support is wide.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.idlayr.com/get-started/signed-http-messages.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
