KYC Status Change Event
Delivers a user's identity verification status change to your callback URL.
Sent to every callback URL subscribed to the kyc event type when a user's
status changes for one of your financial institution's investing products.
Intermediate states carrying no outcome you can act on are not delivered.
One subscription covers every delivered status: filter on data.kyc_status
(pending, document_requested, approved, rejected, closed,
account_takeover) rather than subscribing per status. A user who has
finished onboarding into a product reports approved for that product,
named in data.product.
Each request carries two headers:
X-InvestiFi-Webhook-Id: the delivery's idempotency key, equal toevent_idand reused across retries. Deduplicate on it.X-InvestiFi-Webhook-Signature:t=<unix_seconds>,v1=<hex digest>. The digest is a hash-based message authentication code (HMAC-SHA256) over the timestamp, a period, and the raw request body, keyed by the signing secret returned from your first subscribe.v1is a scheme tag: match on it rather than assuming one value. The header is sent once your financial institution has a signing secret.
Verify against the raw request body, before parsing it, so the bytes match what was signed. Compare digests in constant time:
import hashlib
import hmac
parts = dict(p.split("=", 1) for p in signature_header.split(","))
signed = f"{parts['t']}.{raw_body.decode()}".encode()
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, parts["v1"]):
raise ValueError("Signature does not match")Delivery is at-least-once. Return any 2xx status to acknowledge; a non-2xx
response or a timeout is retried. links.self points at the authoritative
user record for consumers that would rather refetch than trust the payload.
Each subscription is pinned to the API version it was created through, and
links.self is built at that version: a subscription created via the v1
subscribe endpoint emits /v1/... links, and keeps doing so after newer
API versions ship. When you migrate to a new API version, switch both at
the same time: point your API calls at the new base URL and re-create your
webhook subscriptions through the new version's subscribe endpoint, then
delete the old subscriptions.
- body
- The envelope POSTed to a subscribed callback URL.
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
Example Requests
The envelope delivered to a partner callback URL.
Every webhook shares this shape: data is a convenience so a partner
can act without a callback, and links.self keeps the API the source
of truth for consumers that would rather refetch.
/kycDelete Webhook Subscription
Requires the `manage:webhooks` scope. Unsubscribe by disabling the subscription so delivery stops. Soft-disable: the record is kept with `enabled=False` and `disabled_at` set. Disabling an already-disabled subscription is a no-op and still returns 204 No Content, so unsubscribe is idempotent.
Send Test Webhook
Requires the `manage:webhooks` scope. Send a sample event to the subscription's callback URL and report what it answered. The sample is signed and shaped exactly like a real delivery, so the response tells a partner whether their signature verification works before any real event depends on it. The payload describes a synthetic user and is flagged `is_test`, so it must never be processed as a real event. Delivery is one attempt with no retries.

