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