Skip to main content

Generic Webhook — Flow

How a webhook gets delivered - the full flow

Understanding the end-to-end sequence
1. Admin registers client config via Configuration API
(webhook URL, headers, HMAC secret, workflow routing)
↓ stored in DynamoDB

2. Event happens (transaction completes, link started, etc.)


3. Producer (audit-portal or link-kyc) writes webhook.json to S3
audit-portal → bucket: prod-generic-webhook-ind
link-kyc → bucket: hv-generic-webhook


4. S3 notifies SQS queue


5. SQS triggers Delivery Service Lambda (in batches)


6. For each SQS message, Lambda:
a. Parses message → extracts S3 bucket + key
b. Fetches webhook.json from S3
c. Looks up client config in DynamoDB (appId + eventType)
d. Checks isActive - skips if false
e. Removes appId from payload if deleteAppId = true
f. Generates HMAC-SHA256 signature
g. Resolves target URL(s) via workflow routing
h. Logs pre_delivery to Redshift
i. POSTs payload to client's webhook URL
j. Logs post_delivery (or failure) to Redshift


7. Failed messages are returned to SQS for retry
(only the failed ones - successful ones are not re-processed)

Generic Webhook delivery flow sequence


The two important event producers right now
ProducerRepoWhat it sends
Audit Portalgitlab.com/hvlabs/kyc/audit-portalKYC transaction results — final status, intermediate state, manual review decisions, CPR resets
Link-KYCgitlab.com/hvlabs/kyc/global-dkycLink-level events — when a user starts a link-kyc flow, or tries to use an expired link
The event types

Audit Portal

Event TypeWhen it fires
FINISH_TRANSACTION_WEBHOOKTransaction is fully complete — all KYC checks synced, final status reached (approved/declined)
INTERMEDIATE_TRANSACTION_WEBHOOKTransaction in kyc_in_progress state — not all checks done yet
MANUAL_REVIEW_STATUS_UPDATEA reviewer manually approves or declines in the portal
APPLICATION_STATE_RESETCPR (Cross-Platform Resume) state reset — subtypes: COMPLETE_STATE_RESET, RESET_FROM_SPECIFIC_STEP

A webhook only fires if that event type is listed in the client's events array in audit-portal's configs table.

Event TypeWhen it firesCan be disabled?
START_TRANSACTION_WEBHOOKUser opens a link and starts the KYC flow (POST /v1/link-kyc/start)Yes — startTransactionWebhookEnabled in portal config
EXPIRED_LINK_USEDUser attempts to use a link that has already expiredNo — always fires

What gets saved to S3

Every webhook event is saved as webhook.json to S3 before delivery. The file has two sections:

{
"metadata": {
"appId": "55a3a6",
"transactionId": "txn-abc-123",
"eventType": "FINISH_TRANSACTION_WEBHOOK",
"eventId": "evt-456",
"workflowId": "premium-kyc"
},
"payload": {
"appId": "55a3a6",
"transactionId": "txn-abc-123",
"applicationStatus": "auto_approved",
"eventId": "evt-456",
"eventVersion": "1.0.0",
"eventTime": "2024-12-04T10:00:00.000Z",
"eventType": "FINISH_TRANSACTION_WEBHOOK"
}
}
SectionUsed forSent to client?
metadataInternal routing — DynamoDB lookup, HMAC generation, Redshift loggingNo
payloadThe actual webhook bodyYes

Webhook JSON format

S3 paths:

ProducerBucketPath
Audit Portalprod-generic-webhook-indaudit-portal/<appId>/<YYYY-MM-DD>/<eventId>/<eventType>/webhook.json
Link-KYChv-generic-webhooklink-kyc/<appId>/<YYYY-MM-DD>/<eventId>/<eventType>/webhook.json

What the client receives

POST https://client.example.com/webhook
Content-Type: application/json
Authorization: Bearer client-token ← from their DynamoDB config headers
x-hv-signature: a1b2c3d4e5f6... ← HMAC-SHA256 signature

{
"appId": "55a3a6",
"transactionId": "txn-abc-123",
"applicationStatus": "auto_approved",
"eventId": "evt-456",
"eventVersion": "1.0.0",
"eventTime": "2024-12-04T10:00:00.000Z",
"eventType": "FINISH_TRANSACTION_WEBHOOK"
}

Only the payload object is sent. metadata is never exposed to the client.


HMAC signature

Every webhook is signed so the client can verify it genuinely came from HyperVerge.

How we generate it
signedPayload = "{transactionId}_{fast-json-stable-stringify(payload)}"
signature = HMAC-SHA256(signedPayload, secret)
header = x-hv-signature: {signature}

fast-json-stable-stringify sorts JSON keys alphabetically before stringifying — this ensures the same payload always produces the same signature regardless of key insertion order.

HMAC signature generation

Workflow-based routing

Clients can route webhooks to different URLs depending on which workflow the transaction used.

Example: A client has standard-kyc and premium-kyc workflows and wants premium transactions delivered to a separate endpoint.

DynamoDB config
{
"appId": "55a3a6",
"eventType": "FINISH_TRANSACTION_WEBHOOK",
"webhookUrl": "https://client.com/default-webhook",
"workflows": {
"standard-kyc": ["https://client.com/standard-webhook"],
"premium-kyc": ["https://client.com/premium-webhook1", "https://client.com/premium-webhook2"]
}
}

Routing logic:

  1. If metadata.workflowId exists AND matches a key in workflows AND the URL list is non-empty → use those URLs (in parallel)
  2. Otherwise → use default webhookUrl
Was this helpful?
Ask AI

Ask anything about the internal documentation

AI answers are based on internal documentation. Verify critical information.