Skip to main content

Webhooks

Webhooks let your server receive real‑time notifications when important events happen in Passage. Register a publicly reachable HTTPS endpoint for your webhooks and Passage will POST a JSON payload for every event.

Security

  • Use HTTPS for your endpoint.
  • Optional: Verify idempotency using the id field of each webhook to avoid duplicate processing

Events

Every time a connection changes status, passage will send your service a webhook. Connection statuses are listed below.

type ConnectionStatus =
"pending" |
"connecting" |
"connected" |
"canceled" |
"rejected" |
"failed" |
"data_processing" |
"data_available" |
"data_partially_available" |
"inactive"

If you just care about receiving data, you will want to listen for the data_available status.

Top-level payload

Each webhook shares a common envelope:

{
"id": "evt_123",
"timestamp": "2025-01-01T00:00:00.000Z",
"type": "Connection.Updated",
"data": { /* event-specific object */ }
}
  • id (string): Unique event id. Treat as idempotency key.
  • timestamp (ISO-8601 string): When the event occurred.
  • type (string): One of the event types above.
  • data (object): Event-specific payload shown below.

Connection events

The body of the webhook will contain a resources section that will tell you the status of each resource and a url that you can use to fetch that resource

Example full payload:

{
"id": "evt_9b1f",
"timestamp": "2025-01-01T12:34:56.000Z",
"type": "Connection.Updated",
"data": {
"connectionId": "123",
"status": "data_available",
"resources": [
{
"type": "trip",
"url": "https://api.getpassage.ai/connections/123/resources/trip",
"status": "data_available",
"operation": "read",
"startedAt": "2025-01-01T00:00:00.000Z",
"completedAt": "2025-01-01T00:01:00.000Z"
},
{
"type": "account_info",
"url": "https://api.getpassage.ai/connections/123/resources/account_info",
"status": "data_available",
"operation": "read",
"startedAt": "2025-01-01T00:00:00.000Z",
"completedAt": "2025-01-01T00:01:00.000Z"
}
]
}
}

Handling webhooks

Pseudo-code example in Node/Express:

import type { Request, Response } from 'express';

export async function handleWebhook(req: Request, res: Response) {
const event = req.body; // Ensure raw body parsing if verifying signatures later

// De-duplicate using event.id
// if (await hasProcessed(event.id)) return res.sendStatus(200);

if (event.type != "Connection.Updated") {
return;
}

const data = event.data

switch (data.status) {
case 'data_available': {
const { connectionId, resources } = data
for (const resource of resources) {
if (resource.status == 'data_available'){
fetchResource(resource);
}
}
break;
}
default:
// ignore unknown types safely
break;
}

res.sendStatus(200);
}

Testing locally

  • Expose your local server using a tunneling tool (e.g., ngrok http 3000).
  • Configure your webhook URL in Passage settings to the public tunnel URL.
  • Log received events and store id values to ensure idempotency.