Skip to Content
Passage ConnectGuidesWebhook Verification

Webhook Verification

How to verify the authenticity of Passage Connect webhooks.

Overview

Webhook requests include an X-Passage-Signature header containing an ES256-signed JWT. The JWT payload includes a SHA-256 hash of the request body, allowing you to verify both authenticity and integrity.

Step 1: Extract the signature

Read the JWT from the X-Passage-Signature header and the timestamp from X-Passage-Timestamp:

const signature = request.headers.get("X-Passage-Signature"); const timestamp = request.headers.get("X-Passage-Timestamp");

Step 2: Decode the JWT header

Parse the JWT header to get the kid (key ID):

const [headerB64] = signature.split("."); const header = JSON.parse(atob(headerB64)); const kid = header.kid;

Step 3: Fetch the public key

const res = await fetch( "https://connect.getpassage.ai/webhook_verification_key/get", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key_id: kid }), }, ); const { key } = await res.json(); // PEM public key

Step 4: Verify the JWT signature

const publicKey = await crypto.subtle.importKey( "spki", pemToArrayBuffer(key), { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"], ); // Verify the ES256 signature over header.payload const [headerB64, payloadB64, sigB64] = signature.split("."); const data = new TextEncoder().encode(`${headerB64}.${payloadB64}`); const sig = base64urlDecode(sigB64); const valid = await crypto.subtle.verify( { name: "ECDSA", hash: "SHA-256" }, publicKey, sig, data, );

Step 5: Verify the body hash

After verifying the JWT signature, check that the body hash matches:

const payload = JSON.parse(atob(payloadB64)); const bodyHash = await sha256Hex(requestBody); if (payload.request_body_sha256 !== bodyHash) { throw new Error("Body hash mismatch"); }

Security notes

  • Always verify webhook signatures before processing
  • Cache the public key to avoid fetching on every webhook
  • Check the iat claim to reject stale webhooks

Next steps

Last updated on