Explore Knowledge Base

Webhook settings

11. 02. 2026

Webhook Configuration

Webhooks can be configured with the following environment variables:

Variable

Description

Default value

WEBHOOK_TIMER_ENABLED

Enable or disable the webhook dispatcher timer

false

WEBHOOK_TIMER_SCHEDULE_

EXPRESSION

Execution interval for the webhook dispatcher timer as cron expression

0 0/1 * ? * *

WEBHOOK_TIMER_EVENT_

DELIVERY_TTL

Maximum time that webhook events are retained in the system (24 hours)

PT24H

WEBHOOK_TIMER_EVENT_DELIVERY_

RESEND_DELAY

Time to wait before attempting to resend a failed webhook event

PT30S

WEBHOOK_TIMER_EVENT_DELIVERY_

MAX_ATTEMPTS

The maximum number of times the system will attempt to deliver a webhook event

5

WEBHOOK_TIMER_EVENT_DELIVERY_

EXPONENTIAL_BACKOFF

Enable exponential backoff for webhook retry attempts

true

WEBHOOK_URL

The base URL where webhook callbacks will be sent

https://your-domain.com/api/v1/webhook/callback/url

WEBHOOK_SHARED_SECRET

The shared secret used to sign webhook requests

unique-shared-secret-key

WEBHOOK_TIMEOUT_CONNECT

Time to wait for establishing a connection to the webhook endpoint in milliseconds

5000

WEBHOOK_TIMEOUT_REQUEST

Maximum time to wait for the webhook endpoint to respond in milliseconds

5000

WEBHOOK_EVENT_TYPES

An array of event types that will trigger webhook notifications

KYC_CHECK_REQUIRED,

GATE_TOPUP_INITIATED,

GATE_WITHDRAWAL_INITIATED

Retry and Backoff Strategy

Exponential Backoff Calculation

When WEBHOOK_TIMER_EVENT_DELIVERY_EXPONENTIAL_BACKOFF=true, the system calculates progressively longer delays between retry attempts using an exponential backoff algorithm.

Formula

delay = initialDelay × 2^attempt

Where:

  • initialDelay is the value of WEBHOOK_TIMER_EVENT_DELIVERY_RESEND_DELAY (default: 30 seconds)

  • attempt is the current attempt number

  • The maximum exponent is capped at 30 to prevent overflow

Calculation Examples

With default settings (WEBHOOK_TIMER_EVENT_DELIVERY_RESEND_DELAY=PT30S):

| Attempt | Multiplier | Delay Calculation | Actual Delay |
|---------|------------|-------------------|--------------|
| 1 | - | - | no delay |
| 2 | 2^1 = 2 | 30s × 2 | 1 minute |
| 3 | 2^2 = 4 | 30s × 4 | 2 minutes |
| 4 | 2^3 = 8 | 30s × 8 | 4 minutes |
| 5 | 2^4 = 16 | 30s × 16 | 8 minutes |
| 6 | 2^5 = 32 | 30s × 32 | 16 minutes |

Real-world case delay could be longer because system will perform delivery on the next timer execution, so you should take to account timer settings. For example if the timer configured to be executed each minute and delay is 8 min for an attempt, real sent time will be in a range 8-9 min (delay + wait for timer to be executed).

Fixed Delay

When WEBHOOK_TIMER_EVENT_DELIVERY_EXPONENTIAL_BACKOFF=false, the system uses a fixed delay between all retry attempts:

delay = WEBHOOK_TIMER_EVENT_DELIVERY_RESEND_DELAY (constant for all attempts)

For example, with WEBHOOK_TIMER_EVENT_DELIVERY_RESEND_DELAY=PT30S:

  • Attempt 1: no delay

  • Attempt 2: 30 seconds

  • Attempt 3: 30 seconds

Use fixed delay when: You need predictable, consistent retry intervals (e.g., testing, strict SLA requirements).

Use exponential backoff when: You want to reduce load on failing systems and improve overall system resilience (recommended for production).

Webhook statuses

The implementation has specific delivery logic for HTTP response handling behaviour:

  • 2xx response code → status = DELIVERED

  • 4xx response code → status = FAILED (permanent, no retry)

  • 5xx / network error → status = PENDING (will retry) or FAILED if max attempts exceeded

Signature Verification

All webhook requests are signed using HMAC-SHA256 to ensure authenticity and integrity. Webhook consumers must verify the signature before processing the event.

How the Signature is Created

The signature is computed using the following steps:

  1. Concatenate: timestamp + webhook_url + json_body

  2. Compute HMAC-SHA256 hash using the shared secret

  3. Encode the result in Base64

Example:

timestamp = "1735689600"
url = "https://your-domain.com/api/v1/webhook/callback/url"
body = "{\"eventType\":\"KYC_CHECK_REQUIRED\",\"resourceId\":\"123\",...}"
message = timestamp + url + body
signature = Base64(HMAC-SHA256(shared_secret, message))

HTTP Headers

The webhook request includes the following headers:

  • Authorization: `HMAC-SHA256 {base64_signature}` – The computed signature

  • Timestamp: `{unix_timestamp}` – Unix epoch timestamp in seconds

  • Content-Type: `application/json`

Verification Steps

To verify the webhook signature on the client side:

Step 1: Extract Headers

String authHeader = request.getHeader("Authorization");
String timestampHeader = request.getHeader("Timestamp");
String signature = authHeader.replace("HMAC-SHA256 ", "");

Step 2: Read Request Body

String requestBody = // Read raw request body as string

Step 3: Reconstruct the Message

String webhookUrl = "https://your-domain.com/api/v1/webhook/callback/url";
String message = timestampHeader + webhookUrl + requestBody;

Step 4: Compute Expected Signature

Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
sharedSecret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
hmacSha256.init(secretKey);
byte[] hash = hmacSha256.doFinal(message.getBytes(StandardCharsets.UTF_8));
String expectedSignature = Base64.getEncoder().encodeToString(hash);

Step 5: Compare Signatures

if (!MessageDigest.isEqual(
expectedSignature.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8)
)) {
throw new SecurityException("Invalid webhook signature");
}

  1. Use constant-time comparison: Always use MessageDigest.isEqual() or equivalent to prevent timing attacks

  2. Preserve raw body: Verify signature against the raw request body exactly as received (no parsing/reformatting)

  3. Timestamp validation: Consider rejecting requests with timestamps too far in the past (e.g., > 5 minutes) to prevent replay attacks

  4. Shared secret security: Store the shared secret securely (environment variables, secret management systems)

  5. HTTPS only: Always use HTTPS to protect the shared secret and webhook data in transit

Webhook notifications examples

Transaction notifications

Webhook notification body example

{
"id": "019c0a3d-91f0-7246-8a69-cc09293ae9d6",
"eventType": "GATE_TOPUP_INITIATED", //GATE_WITHDRAWAL_INITIATED
"resourceId": "019c0a3d-91ef-70b2-b101-957feb1eab49",
"createdAt": "2026-01-29T14:52:13.168Z",
"data": {
"providerId": "019bff88-19ee-7466-88b3-29cd68ba08b3"
}
}

KYC notifications

Webhook notification body example

{
"id": "019c0a44-3775-7b72-8d2a-b49daefd82d1",
"eventType": "KYC_CHECK_REQUIRED",
"resourceId": "019c0a40-f6b9-739a-862e-eee0a4a85367",
"createdAt": "2026-01-29T14:59:28.757Z",
"data": {}
}

System settings

KYC and Gate providers should be configured in an appropriate way to send webhook notifications.

KYC provider configuration

Webhook will be sent if the provider is specified in active-provider-name and externalIntegration: true for this provider.

Example:

compliance:
document-verification:
auto-enabled: false
active-provider-name: SUMSUB
providers:
- name: MANUAL
organization-types:
- individual
- business
- shareholder
verification-item-types:
- USER_ID
- FIRST_NAME_PLAIN
- LAST_NAME_PLAIN
- DATE_OF_BIRTH
- ORGANIZATION_TYPE
- CREDENTIAL
- COUNTRY
- ZIPCODE
- REGION
external-integration: false
- name: SUMSUB
organization-types:
- individual
- business
- shareholder
verification-item-types:
- USER_ID
- FIRST_NAME_PLAIN
- LAST_NAME_PLAIN
- DATE_OF_BIRTH
- ORGANIZATION_TYPE
- CREDENTIAL
- COUNTRY
- ZIPCODE
- REGION
external-integration: true

Webhook will not be sent if the provider is specified in active-provider-name and externalIntegration: true for another provider.

Example:

compliance:
document-verification:
auto-enabled: false
active-provider-name: MANUAL
providers:
- name: MANUAL
organization-types:
- individual
- business
- shareholder
verification-item-types:
- USER_ID
- FIRST_NAME_PLAIN
- LAST_NAME_PLAIN
- DATE_OF_BIRTH
- ORGANIZATION_TYPE
- CREDENTIAL
- COUNTRY
- ZIPCODE
- REGION
external-integration: false
- name: SUMSUB
organization-types:
- individual
- business
- shareholder
verification-item-types:
- USER_ID
- FIRST_NAME_PLAIN
- LAST_NAME_PLAIN
- DATE_OF_BIRTH
- ORGANIZATION_TYPE
- CREDENTIAL
- COUNTRY
- ZIPCODE
- REGION
external-integration: true

Gate provider configuration

Webhook for transaction notification will be sent if the provider is linked to a custom gate (gate with "custom": true) and externalIntegration: true for this provider.

Create a gate provider: POST /gate-providers. During creation externalIntegration: true can be specified.

Set externalIntegration: true PATCH ​/gate-providers​/{gateProviderId}

Link gate provider to the custom gate: PATCH ​/gate-providers​/{gateProviderId}​/link