Agent skill

Footprint Webhooks Skill

Receive and verify Footprint webhooks (identity verification / KYC, delivered via Svix). Use when setting up Footprint webhook handlers, debugging svix-signature verification, or handling events like footprint.onboarding.completed, footprint.onboarding.status_changed, and footprint.watchlist_check.completed.

Install this skill

npx skills add hookdeck/webhook-skills --skill footprint-webhooks


Footprint (identity verification and KYC) delivers webhooks through Svix. Every delivery is an HTTP POST with a JSON body and three signature headers: svix-id, svix-timestamp, and svix-signature. The event name is the event_kind field in the body. Footprint's own pages point to Svix's guides for receiving and verifying; its event catalog is hosted on svix.com.

When to Use This Skill

  • How do I receive Footprint webhooks?
  • How do I verify Footprint webhook signatures (svix-signature)?
  • How do I handle footprint.onboarding.completed when a user's status was pending?
  • How do I react to footprint.watchlist_check.completed (continuous AML monitoring)?
  • Why is my Footprint webhook signature verification failing, or rejecting Hookdeck retries?

Verification (core)

Signed content is {svix-id}.{svix-timestamp}.{raw body}. Svix signs it with HMAC-SHA256 using the base64-decoded secret (the part after whsec_), base64-encodes the result, and sends one or more space-delimited v1,<signature> entries. Footprint recommends Svix's verification guide and the official svix library, which compares signatures in constant time.

import { Webhook } from 'svix'; // svix 2.x is ESM-only

const wh = new Webhook(process.env.FOOTPRINT_WEBHOOK_SECRET); // "whsec_..."
// rawBody: the raw Buffer/string, never re-serialized JSON
wh.verify(rawBody, {
  'svix-id': req.headers['svix-id'],
  'svix-timestamp': req.headers['svix-timestamp'],
  'svix-signature': req.headers['svix-signature'],
}); // throws on a bad signature or a timestamp outside the window
const event = JSON.parse(rawBody.toString('utf8')); // parse only after verify passes
from svix.webhooks import Webhook, WebhookVerificationError

wh = Webhook(os.environ["FOOTPRINT_WEBHOOK_SECRET"])
wh.verify(raw_body, {"svix-id": msg_id, "svix-timestamp": ts, "svix-signature": sig})

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Secret format is unconfirmed on Footprint's side. whsec_ is Svix's secret format. Footprint's webhook pages never show the secret, so treat the whsec_ prefix as an assumption from Svix and check the value you get from your Footprint dashboard.

Hookdeck retries and replays hit the 5-minute window

The svix and standardwebhooks libraries always enforce a fixed 5-minute timestamp window, and it can't be configured. Hookdeck retries and replays resend the original svix-timestamp, so the library rejects any retry or replay that happens more than 5 minutes after the original delivery. If you verify behind Hookdeck (or replay old events), use the manual verifier in references/verification.md, where the window can be dropped or widened. Deduplicate on svix-id instead of relying on the timestamp.

Common Event Types

The event name is event_kind in the JSON body.

EventTriggered When
footprint.onboarding.completedA user's onboarding reaches a terminal status (pass, fail, none). Footprint recommends subscribing to this one
footprint.onboarding.incompleteAn onboarding started via POST /onboardings still has unmet requirements
footprint.onboarding.status_changedA user's status changed after onboarding. Deprecated in favor of onboarding.completed and user.manual_review
footprint.watchlist_check.completedA watchlist check ran for a user (needs continuous monitoring on the playbook)
footprint.user.manual_reviewA dashboard user changed the user's status during manual review
footprint.user.info_requestedA dashboard user asked the user for more information during review
footprint.user.bank_link_updatedA user's bank link was updated
footprint.vault.updatedA user or business vault was updated
footprint.entity.entity_status_setA custom status was set, updated, or cleared on a user or business

See references/overview.md for payload fields.

Environment Variables

# Signing secret of the webhook endpoint (Svix format, starts with whsec_)
FOOTPRINT_WEBHOOK_SECRET=whsec_xxxxx

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 footprint --path /webhooks/footprint

Register the tunnel URL as an endpoint in the Footprint dashboard, then complete a test onboarding in sandbox mode to trigger events.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 3, 2026

View on GitHub →