Generic Webhook — API Reference
Developers who need exact request/response specs for the Configuration API.
Configuration API
Do not share these URLs or credentials with clients. These URLs are of the lambdas and not the API gateway URLs.
| Environment | URL |
|---|---|
| Production | https://a4l5wq5l6gjkjrromxx3riovu40lpzcx.lambda-url.ap-south-1.on.aws/ |
| Development | https://zi2btyam4ewqn4pb6kfz4bbkoq0yvwpg.lambda-url.ap-south-1.on.aws/ |
All endpoints use the same base URL. Method (GET / POST / PATCH) determines the operation.

Authentication
Every request must pass two checks — IP whitelist first, then credentials.
1. IP Whitelist
- Controlled by
ALLOWED_IPSenv var (comma-separated list, or*for all) - Checked before credentials — if IP is not allowed, the request is rejected immediately
- If
ALLOWED_IPSis not set at all, every request is rejected
Response (403) — IP not whitelisted:
{
"statusCode": 403,
"status": "failed",
"error": "IP not whitelisted"
}
2. Credentials (choose one method)
Method A — JWT (RS256):
Authorization: Bearer <jwt-token>
- Token verified using
PUBLIC_CERTenv var (RSA public key) appIdextracted from decoded JWT payload- Returns "Token Expired" error if token is expired
Method B — appId/appKey:
appid: <app-id>
appkey: <app-key>
- Validated against
https://auth.hyperverge.co/credentials/authenticate - MD5 digest of appKey used for cache key comparison
- Credentials cached for 15 minutes in memory
- Note: If the auth service returns a non-401 error (e.g. 500, timeout), the request is allowed through — intentional, to prevent auth outages from blocking config operations
Response (401) — Missing or invalid credentials:
{
"statusCode": 401,
"status": "failed",
"error": "Missing/Invalid credentials"
}
POST / — Create webhook config
Creates a new webhook config for a appId + eventType combination.
curl example:
curl --request POST 'https://a4l5wq5l6gjkjrromxx3riovu40lpzcx.lambda-url.ap-south-1.on.aws/' \
--header 'appid: <APP ID>' \
--header 'appkey: <APP KEY>' \
--header 'Content-Type: application/json' \
--data '{
"appId": "55a3a6",
"eventType": "FINISH_TRANSACTION_WEBHOOK",
"isActive": true,
"webhookUrl": "https://client.example.com/webhook",
"product": "link-kyc",
"deleteAppId": false,
"headers": {
"Authorization": "Bearer client-token"
},
"updateSecret": true
}'
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | Client's app ID (becomes DynamoDB partition key) |
eventType | string | Yes | Event type (becomes DynamoDB sort key) |
webhookUrl | string | Yes | Default URL to deliver webhooks to |
product | string | Yes | Product registering this config (e.g. "audit-portal", "link-kyc") |
isActive | boolean | Yes | Whether delivery is enabled |
headers | object | No | Custom HTTP headers to include in every webhook request |
deleteAppId | boolean | No | If true, strips appId from payload before delivery |
updateSecret | boolean | No | If true, generates a new HMAC secret and stores it |
workflows | object | No | Per-workflow URL map: { "workflowId": ["url1", "url2"] } |
Note on updateSecret: If the env var UPDATE_SECRET_DEFAULT_VALUE=yes is set, a secret is always generated even without passing updateSecret: true.
Response (201) — Success:
{
"statusCode": 201,
"status": "success",
"message": "Config record successfully stored in Dynamo DB",
"data": {}
}
If updateSecret: true (or UPDATE_SECRET_DEFAULT_VALUE=yes), data contains the generated secret:
{
"statusCode": 201,
"status": "success",
"message": "Config record successfully stored in Dynamo DB",
"data": {
"secret": "whsecure_a1b2c3d4e5f6..."
}
}
Secret format: whsecure_ + 40 hex characters (crypto.randomBytes(20).toString('hex')).
Response (422) — Validation failure:
{
"statusCode": 422,
"status": "failed",
"error": "\"isActive\" is required"
}
Response (401) — Missing or invalid credentials:
{
"statusCode": 401,
"status": "failed",
"error": "Missing/Invalid credentials"
}
Response (500) — Server error:
{
"statusCode": 500,
"status": "failed",
"message": "Config record couldn't be stored. Please try again later"
}
GET / — Retrieve webhook config
Fetches the stored config for an appId + eventType.
curl example:
curl --request GET 'https://a4l5wq5l6gjkjrromxx3riovu40lpzcx.lambda-url.ap-south-1.on.aws/' \
--header 'appid: <APP ID>' \
--header 'appkey: <APP KEY>' \
--header 'Content-Type: application/json' \
--data '{
"appId": "55a3a6",
"eventType": "FINISH_TRANSACTION_WEBHOOK"
}'
curl example (with secret):
curl --request GET 'https://a4l5wq5l6gjkjrromxx3riovu40lpzcx.lambda-url.ap-south-1.on.aws/' \
--header 'appid: <APP ID>' \
--header 'appkey: <APP KEY>' \
--header 'Content-Type: application/json' \
--data '{
"appId": "55a3a6",
"eventType": "FINISH_TRANSACTION_WEBHOOK",
"shareSecret": true
}'
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | Client's app ID |
eventType | string | Yes | Event type |
shareSecret | boolean | No (default: false) | Include HMAC secret in response |
The secret field is always stripped from the response unless shareSecret: true is explicitly passed.
Response (200) — Success:
{
"statusCode": 200,
"status": "success",
"message": "Config records successfully fetched from Dynamo DB",
"data": {
"product": "link-kyc",
"isActive": true,
"webhookUrl": "https://client.example.com/webhook",
"eventType": "FINISH_TRANSACTION_WEBHOOK",
"appId": "55a3a6",
"headers": { "Authorization": "Bearer client-token" }
}
}
With shareSecret: true, data also includes "secret": "whsecure_...".
Response (422) — Validation failure (e.g. extra field not allowed):
{
"statusCode": 422,
"status": "failed",
"error": "\"product\" is not allowed"
}
Response (422) — Missing required field:
{
"statusCode": 422,
"status": "failed",
"error": "\"eventType\" is required"
}
Response (401) — Missing or invalid credentials:
{
"statusCode": 401,
"status": "failed",
"error": "Missing/Invalid credentials"
}
PATCH / — Update webhook config
Updates fields on an existing config. Only provided fields are changed.
curl example:
curl --request PATCH 'https://a4l5wq5l6gjkjrromxx3riovu40lpzcx.lambda-url.ap-south-1.on.aws/' \
--header 'appid: <APP ID>' \
--header 'appkey: <APP KEY>' \
--header 'Content-Type: application/json' \
--data '{
"appId": "55a3a6",
"eventType": "FINISH_TRANSACTION_WEBHOOK",
"webhookUrl": "https://new-endpoint.example.com/webhook",
"updateSecret": true
}'
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | Lookup key (partition key) — cannot be changed |
eventType | string | Yes | Lookup key (sort key) — cannot be changed |
webhookUrl | string | No | New default webhook URL |
product | string | No | Product identifier |
isActive | boolean | No | Enable or disable delivery |
headers | object | No | Custom HTTP headers |
updateSecret | boolean | No | If true, generates and stores a new HMAC secret |
workflows | object | No | Per-workflow URL map |
appId and eventType identify the record to update and are never modified.
Response (200) — Success:
{
"statusCode": 200,
"status": "success",
"message": "Config record successfully updated in Dynamo DB",
"data": {
"Attributes": {
"webhookUrl": "https://new-endpoint.example.com/webhook"
}
}
}
data.Attributes contains the updated fields (DynamoDB UPDATED_NEW response).
If updateSecret: true, the new secret is returned in data.Attributes.secret.
Response (422) — Validation failure:
{
"statusCode": 422,
"status": "failed",
"error": "\"someKey\" is not allowed"
}
Response (401) — Missing or invalid credentials:
{
"statusCode": 401,
"status": "failed",
"error": "Missing/Invalid credentials"
}
DynamoDB config schema
Table is shared between Configuration API (writes) and Delivery Service (reads).
- Partition key:
appId(String) - Sort key:
eventType(String) - Table name: from
DYNAMODB_TABLE_NAMEenv var (Config API) /dynamoTableNameenv var (Delivery Service)
| Field | Type | Description |
|---|---|---|
appId | String | Client's app ID |
eventType | String | Event type (e.g. FINISH_TRANSACTION_WEBHOOK) |
webhookUrl | String | Default URL to deliver webhooks to |
secret | String | HMAC signing secret (whsecure_ + 40 hex chars) |
headers | Object | Custom HTTP headers to include with every webhook request |
isActive | Boolean | false = paused (no delivery, no retry) |
workflows | Object | Per-workflow URL map: { "workflowId": ["url1", "url2"] } |
deleteAppId | Boolean | true = strip appId from payload before delivery |
product | String | Product that registered this config |
Delivery Service — SQS input format
The Lambda receives SQS messages containing S3 event notifications:
{
"Records": [
{
"messageId": "msg-123",
"body": "{\"Records\":[{\"s3\":{\"bucket\":{\"name\":\"prod-generic-webhook-ind\"},\"object\":{\"key\":\"audit-portal/55a3a6/2024-12-04/evt-123/FINISH_TRANSACTION_WEBHOOK/webhook.json\"}}}]}",
"attributes": {
"SentTimestamp": "1704067200000"
}
}
]
}
The body is a JSON string containing the S3 bucket name and object key.
Delivery Service — Lambda response format
{
"batchItemFailures": [
{ "itemIdentifier": "msg-456" }
]
}
Only failed messageIds are listed. Empty array = all records succeeded. SQS retries only the listed messages (partial batch retry via ReportBatchItemFailures).
Delivery Service — webhook delivery headers
POST {webhookUrl}
Content-Type: application/json
{custom headers from DynamoDB config}
x-hv-signature: {HMAC-SHA256 hex digest}
Timeout: {AXIOS_TIMEOUT}ms (default: 30000)