fastify-xplaid
webhooks.getVerificationKey(keyId)
Retrieve the JWK used to validate Plaid webhook payload signatures.
webhooks.getVerificationKey(keyId)
Retrieves the JWK (JSON Web Key) that Plaid uses to sign webhook payloads. The keyId comes from the Plaid-Verification header on each incoming webhook request. Cache the key and only re-fetch when you see a new keyId.
Signature
fastify.xplaid.webhooks.getVerificationKey(
keyId: string
): Promise<{ key: WebhookVerificationKey; requestId: string }>
Params
| Name | Type | Required | Description |
|---|---|---|---|
keyId | string | Yes | The key ID from the Plaid-Verification header of the incoming webhook. |
Returns
Promise<{ key: WebhookVerificationKey, requestId: string }>
| Field | Type | Description |
|---|---|---|
key.alg | string | Signing algorithm, e.g. "ES256". |
key.created_at | number | Unix timestamp when the key was created. |
key.crv | string | Elliptic curve name, e.g. "P-256". |
key.expired_at | number | null | Unix timestamp when the key expired, or null if still active. |
key.kid | string | Key ID matching the Plaid-Verification header value. |
key.kty | string | Key type, e.g. "EC". |
key.use | string | Intended use, "sig". |
key.x | string | EC public key x-coordinate (base64url). |
key.y | string | EC public key y-coordinate (base64url). |
Throws
[xPlaid] keyId is required and must be a non-empty string- Plaid API errors — shape:
{ message, statusCode, plaidError }.
Examples
Basic — fetch verification key
const { key } = await fastify.xplaid.webhooks.getVerificationKey(keyId);
console.log(key.alg); // "ES256"
Realistic — webhook receiver with signature verification
// Simple in-process key cache
const keyCache = new Map();
fastify.post("/webhooks/plaid", {
config: { rawBody: true }, // enable rawBody for signature verification
}, async (request, reply) => {
const keyId = request.headers["plaid-verification"];
if (!keyId) return reply.status(400).send({ error: "Missing verification header" });
// Fetch and cache the JWK
let verificationKey = keyCache.get(keyId);
if (!verificationKey) {
try {
const { key } = await fastify.xplaid.webhooks.getVerificationKey(keyId);
verificationKey = key;
keyCache.set(keyId, key);
} catch (error) {
request.log.error({ keyId }, "Failed to fetch webhook verification key");
return reply.status(401).send({ error: "Could not verify webhook" });
}
}
// Verify the JWT signature (use jose or similar)
const isValid = await verifyWebhookSignature(request.rawBody, verificationKey);
if (!isValid) {
request.log.warn("Invalid Plaid webhook signature");
return reply.status(401).send({ error: "Invalid signature" });
}
// Route by event type
const { webhook_type, webhook_code, item_id } = request.body;
request.log.info({ webhook_type, webhook_code, item_id }, "Plaid webhook received");
if (webhook_type === "TRANSACTIONS" && webhook_code === "SYNC_UPDATES_AVAILABLE") {
await enqueueTransactionSync(item_id);
} else if (webhook_type === "ITEM" && webhook_code === "PENDING_EXPIRATION") {
await notifyUserReauthRequired(item_id);
}
return reply.send({ received: true });
});
See also
- webhooks.update(accessToken, webhookUrl) — Register or change the webhook URL for a Plaid Item.
- transactions.sync(accessToken, options?) — Triggered by
TRANSACTIONS / SYNC_UPDATES_AVAILABLEwebhooks. - sandbox-create-public-token — Fire test webhook events without real bank activity.
- fastify-xplaid overview — Plugin setup and options.
AI Context
package: "@xenterprises/fastify-xplaid"
method: fastify.xplaid.webhooks.getVerificationKey(keyId)
use-when: Retrieve the JWKS verification key to validate incoming Plaid webhook signatures — cache by keyId
returns: { key: WebhookVerificationKey, requestId }
