- Build an HTTPS receiver that preserves the raw request body.
- Add the endpoint and choose its event subscriptions.
- Install the Karsa-generated Ed25519 public key in your receiver.
- Complete Karsa’s endpoint-verification challenge.
- Send a test event and inspect its delivery history.
- Enable production event processing and handle retries safely.
Before you begin
You need:- A source API credential or access to Settings → Integrations in Karsa.
- A publicly reachable HTTPS endpoint for production.
- A receiver that can read the unmodified request bytes and return a
2xxquickly. - A secure location for the Ed25519 public key and endpoint metadata.
karsa:webhooks:manage in both the issued token and the tenant service-account grant.
You can create multiple destinations for one ingestion source and select subscriptions separately
for each destination.
Events available in version 1
KYB payloads contain source identity, customer subject ID, evaluation ID, workflow-run ID, status,
and a bounded outcome or failure code.
data.kybRun repeats the run as
Trigger a KYB run documents it: its public status, customer IDs, result summary,
and failure code. They exclude customer profiles, documents, raw provider
evidence, model output, credentials, and signing secrets.
1. Obtain the verification public key
Karsa generates a dedicated Ed25519 signing-key pair when you create an endpoint. Karsa retains the private key encrypted in its backend and never accepts, returns, or exposes private signing-key material through the API or web app. Copy the public key, SHA-256 fingerprint, and key version from the created endpoint into the configuration used by your receiving service. The public key is verification material, not a shared secret. Compare its fingerprint when moving it between environments.You do not generate or upload a key pair. Possession of Karsa’s public key lets your backend
verify signatures but cannot be used to create valid Karsa signatures.
2. Implement the receiver
Create aPOST route that:
- Reads the request as raw bytes.
- Extracts the Karsa signature headers.
- Selects the public key identified by
Karsa-Webhook-Key-Version. - Verifies the Ed25519 signature before parsing JSON.
- Returns the verification challenge when the event type is
webhook.endpoint.verification. - Stores the immutable event ID as a deduplication key.
- Queues business processing and returns a
2xxpromptly.
Express example
acceptWebhookEventOnce with your durable inbox or queue operation. Do not acknowledge a
new event until it has been durably accepted, and return a 2xx for an event ID already accepted.
3. Add the endpoint in Karsa
In the web app:- Open Settings → Integrations.
- Select the ingestion source that owns the endpoint.
- In Webhooks, choose Add endpoint.
- Enter a recognizable name and the receiver’s HTTPS URL.
- Select the KYB event types the destination should receive.
- Save the endpoint.
- Copy the generated public key into your receiver configuration.
PENDING_VERIFICATION. Karsa does not enqueue normal KYB deliveries for it
until verification succeeds.
Changing an active endpoint URL returns it to PENDING_VERIFICATION. Changing a disabled
endpoint URL keeps it DISABLED, clears its prior verification, and requires an explicit enable
followed by verification. Changing only its name or event subscriptions preserves its verification
state. Disabling an endpoint stops new events from being queued for that destination; already
queued deliveries continue using their pinned URL and signature.
API alternative
You can create the same endpoint with a source-authenticated request:4. Verify the endpoint
Choose Verify endpoint in the web app or call:webhook.endpoint.verification request to the configured URL. After verifying
the signature, return a 2xx JSON response containing the exact challenge from the body:
DISABLED back to ACTIVE. If its URL changed while disabled, enabling it
moves it to PENDING_VERIFICATION instead of activating it.
5. Send a test event
After verification, choose Send test. The test uses the normal event ledger, signing code, durable delivery workflow, retry rules, and history—not a separate connectivity check. Your receiver gets awebhook.test event. Confirm that:
- Signature verification succeeds against the advertised key version.
- The header event ID and event type match the JSON body.
- The event ID is stored as a deduplication key.
- Your receiver returns a
2xxafter durable acceptance. - The Karsa delivery history shows
DELIVERED.
Request headers
Event envelope
Every KYB delivery uses a stable version 1 envelope:id for deduplication. Use createdAt and your own domain rules when reconciling state because
events for different customers and endpoints are not globally ordered.
Delivery and retry behavior
Delivery is at least once. Your receiver can observe a duplicate if it accepted a request but Karsa could not record that acceptance.
Automatic retries continue for up to 24 hours by default. The event ID, raw body, signing
timestamp, key version, and signature remain byte-for-byte identical across automatic and manual
retries. Searchable history is available for 30 days.
Because the timestamp is intentionally stable across retries, use the immutable event ID—not a
short arrival-time cutoff—as the primary replay defense. You can enforce a timestamp policy that
matches your integration, but it must allow Karsa’s documented retry window or valid retries will
be rejected.
Karsa re-resolves and validates DNS before every attempt, requires HTTPS in production, blocks URL
credentials and private, loopback, link-local, documentation, multicast, and cloud-metadata address
ranges, and caps response time and response-body size.
Which endpoints receive an event
When a KYB lifecycle event (kyb.evaluation.queued, kyb.evaluation.completed, or
kyb.evaluation.failed) occurs, Karsa creates a delivery for every endpoint that is ACTIVE and
subscribed to that event type at that moment. Events are not queued for endpoints that are
PENDING_VERIFICATION or DISABLED, and they are not sent automatically when the endpoint is
verified or enabled. Finish verification before you trigger work whose webhooks you need, or send
the events it missed afterwards with
Replay missed events. A webhook.test
event is different: it is sent only to the endpoint you request it for, which must be ACTIVE.
A queued delivery keeps the destination URL, raw body, timestamp, and signature it was created
with, so deliveries already queued continue to retry the previous URL until they succeed or
dead-letter. Changing the URL of an ACTIVE endpoint moves it back to PENDING_VERIFICATION: the
new URL receives only events that occur after it is verified, and events in between are not sent to
either URL.
Treat webhooks as notifications and the API as the source of truth. After downtime, a pending or
disabled endpoint, or a URL change, list the events you missed with GET /api/ingestion/webhook-events
as described in Catch up on missed events. For a known run,
GET /api/ingestion/kyb-runs/{id} returns its current state.
Catch up on missed events
Karsa records every KYB lifecycle event for your ingestion source when it occurs, whether or not an endpoint received it.GET /api/ingestion/webhook-events lists those events for the calling source
credential, oldest first. Each item has the same fields and values as the envelope that was (or
would have been) delivered, including the same id as Karsa-Webhook-Id, so feed it through the
same inbox and deduplication as webhook deliveries. JSON key order can differ from the delivered
body, and items carry no signature: the authenticated API response is the proof of origin, so do
not run webhook signature verification on them.
- Pass
nextCursorascursorto read the next page.nextCursorisnullon the last page. typefilters by event type; repeat it or separate values with commas.webhook.testevents are excluded unless you request them. Endpoint verification challenges are not recorded.- Events are listed for 30 days, the same window as delivery history. Older events and a
sincebefore that window are not returned. - To resume later, pass the
createdAtof the last event you stored assince, starting a few minutes earlier: an event can become visible shortly after itscreatedAt. Skip IDs you already stored.
GET /api/ingestion/webhook-events/{eventId} returns one event, or 404 with
WEBHOOK_EVENT_NOT_FOUND when it does not belong to your source or is outside the window.
Replay missed events
After an endpoint is verified or re-enabled, you can have Karsa deliver the events it missed instead of reading them from the events API. Choose Replay missed events on the endpoint in the web app (supervisors only), or call:- Karsa queues a delivery to this endpoint for every recorded event of your source that was created
at or after
since, has a type the endpoint subscribes to now, and was never queued for this endpoint. Events the endpoint already has a delivery for, in any state, are counted inskippedAlreadyDeliveredand not sent again; use manual retry for dead-lettered ones.webhook.testevents are never replayed. - The endpoint must be
ACTIVE; otherwise the call returns409withWEBHOOK_NOT_ACTIVE. sinceis limited to the 30-day history window; an earlier value starts at the window.- Each call queues at most 500 events, oldest first. When
cappedistrue, call again with the samesinceto queue the rest. Repeating a call never queues an event twice. - A replayed delivery has the original event ID in
Karsa-Webhook-Idand the original envelope as its body, so your inbox deduplicates it like any retry. JSON key order can differ from the body other endpoints received. - A replayed delivery is signed when it is queued, with the endpoint’s current active key, and
Karsa-Webhook-Timestampis that time rather than the event’screatedAt. A replay is a new delivery with its own full retry window, and within it every attempt keeps the same timestamp and signature, so a timestamp policy that allows the retry window also accepts replays of older events. - OIDC service principals need the
karsa:webhooks:managescope. The call is audit-logged and shares your source’s request rate limit.
Duplicate handling and ordering
Apply a unique database constraint to the event ID. A safe receiver transaction is:- Verify the signature and header/body agreement.
- Insert the event ID and raw payload into an inbox table with a unique constraint.
- If the event ID already exists, return a
2xxwithout applying it again. - Commit the inbox record.
- Process the event asynchronously and idempotently.
createdAt and the lifecycle state rather than assuming the last request received
is the newest state.
Rotate signing keys
Choose Rotate key in the web app or use the rotate-key API. Karsa generates and retains the new private key and publishes the new public key version. The new version becomes active immediately. The previous public key remains inOVERLAP for at least the configured delivery retry window—24
hours by default and configurable up to 168 hours. The web app requests the full 168-hour overlap
so rotation remains safe for every supported retry configuration. API clients may choose a shorter
overlap only when it is at least the environment’s configured retry window.
During overlap:
- Keep both public-key versions in your receiver’s key map.
- Select the key using
Karsa-Webhook-Key-Version. - Do not replace the old key until its overlap status becomes
RETIRED. - Remember that previously queued deliveries remain pinned to their original key and signature.
Replay a failed delivery
When a delivery reachesDEAD_LETTER, choose Retry in its delivery history. Manual retry does
not create a new event. It preserves the original event ID, raw body, timestamp, key version, and
signature, and creates another append-only delivery attempt.
Deleting an endpoint stops new events and marks pending deliveries as dead-lettered. The deleted
endpoint remains as a read-only archive in the endpoint list for the configured history-retention
period so analysts can still inspect its delivery and attempt history; deleted deliveries cannot be
retried.
Troubleshooting
Go-live checklist
- The production URL uses HTTPS and does not redirect.
- Raw-body signature verification runs before JSON parsing.
- Public keys are selected by key version and stored securely.
- Header event ID/type are compared with the body.
- Event IDs have a unique database constraint.
- Events are durably queued before returning
2xx. - Processing is asynchronous and idempotent.
- Both active and overlapping public keys are accepted during rotation.
- Delivery failures and dead-lettered events are monitored.
- A test event succeeds from Karsa and appears in delivery history.