Webhook

Overview

  • Webhook Notification provides a secure, signed, asynchronous event delivery mechanism for Identity Verification Solution.
  • Webhook and callbackUrl are mutually exclusive. To use signed Webhooks, configure your endpoint in ACMP and do not pass callbackUrl in API requests (Generate URL API, Document API, etc.). If callbackUrl is provided, notifications are sent to that URL instead; signed headers (aai-timestamp, aai-nonce, aai-signature) are not included.
  • All webhook requests are signed to ensure authenticity, integrity, and replay protection.
⚠️

Webhook delivery is not guaranteed.

Clients must implement polling to retrieve final results if webhook delivery fails.

We recommend a polling interval of 10 minutes or greater.

Webhook Endpoint Requirements

Clients must provide an HTTPS endpoint capable of receiving webhook notifications.

Network & Security

  • Only HTTPS endpoints are supported
    HTTP endpoints are not allowed
  • Supported TLS versions:
    • TLS 1.2 or higher

HTTP Request

ItemValue
MethodPOST
Content-Typeapplication/json
Timeout60 seconds

Retry Policy

If a webhook request fails, the system retries delivery automatically.

Failure Conditions

  • Network error
  • Request timeout (> 60 seconds)
  • Any non-2xx HTTP response

Retry Schedule

AttemptDelay
1st retry5 seconds
2nd retry10 seconds
3rd retry20 seconds

Webhooks follow an at-least-once delivery model. Clients must handle duplicate events safely.

Webhook Configuration

Webhook configuration can be managed in ACMP.

  • Webhook HTTPS URL
  • Customer / Account information
  • Shared secret key (secretKey) for request signing

The secret key displayed in ACMP is Base64-encoded. Decode it before use.

Content Signature

All webhook requests are signed using a shared secret.

Supported Signature Algorithms

  • HMAC-SHA256
  • HMAC-SHA512

Signature Headers

HeaderDescription
aai-timestampRequest timestamp (seconds, Unix epoch)
aai-nonceOne-time random string
aai-signatureBase64-encoded signature

What to sign

The exact raw HTTP POST body (UTF-8), as received. Read or cache it before your framework parses JSON. Do not hand-build JSON or re-serialize after parsing (JSON.stringify, toJSONString, etc.) — any formatting change breaks the signature.

How to sign (HMAC-SHA256)

keyBytes       = Base64.decode(sharedSecretFromConsole)
signature      = Base64( HMAC_SHA256(keyBytes, rawRequestBody) )

aai-timestamp and aai-nonce are for replay checks only — not part of the HMAC input.

For HMAC-SHA512, use the algorithm configured for your webhook in ACMP.

Common pitfalls

  • Using the ACMP secret string directly as the HMAC key → decode Base64 first
  • Re-serializing JSON for signing → use the raw body bytes
  • Passing callbackUrl in API calls but expecting aai-signature → use ACMP Webhook only (see Overview)

Signature Verification (Client Side)

Clients must verify webhook requests before processing.

Step 1: Read Headers

timestamp = aai-timestamp
nonce = aai-nonce
signature = aai-signature

Step 2: Validate Timestamp

  • The timestamp must be within ±5 minutes of current time
  • Requests outside this window must be rejected

Step 3: Validate Nonce

  • Each nonce must be unique
  • Recommended: cache nonce values for 5 minutes
  • If the nonce is reused, reject the request

Step 4: Recalculate signature

Apply the formula above (keyBytes + rawRequestBody). Compare the result to the aai-signature header.

Java example

// requestBody = raw HTTP body; secretKey = shared secret from ACMP (Base64, as shown)
public static boolean verify(String requestBody, String secretKey, String signatureHeader)
        throws Exception {
    byte[] keyBytes = Base64.getDecoder().decode(secretKey);
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(keyBytes, "HmacSHA256"));
    String expected = Base64.getEncoder().encodeToString(
            mac.doFinal(requestBody.getBytes(StandardCharsets.UTF_8)));
    return expected.equals(signatureHeader);
}

Step 5: Compare Signatures

  • If signatures do not match → reject with HTTP 400 / 401

Step 6: Respond

ResponseResult
Any 2xxDelivery successful
Non-2xxDelivery failed, retry will be triggered

Event Format

Event Example

{
  "eventId": "uuid",
  "eventType": "COMPLETED",
  "webhookId": "wh_xxx",
  "data": {
    "signatureId": "1234567890"
  }
}

Verify using the raw HTTP body you received; do not copy this example into code.

{
    "eventId": "uuid",
    "eventType": "BUSINESS_VERIFICATION_STATUS",
    "data": {
        "id": "KYBC-201464826803499999",
        "status": "COMPLETED",
        "checkItem": "Basic Business Verification",
        "updatedAt": 1769405823
    }
}
{
    "eventId": "uuid",
    "eventType": "AML_OGS_UPDATE",
    "data": {
      "profileId": "PF_R5BoIxxxxxxxxxxQnnNVqi",
      "caseId": 1998600000000026050,
      "alertId": 2001680000000082882,
      "numberOfNewResults": 0,
      "numberOfUpdatedResults": 1
    }
}
{
    "eventId":"uuid",
    "eventType":"KYB_IDV_EMAIL",
    "data": {
        "email":"[email protected]",
        "directUrl":"",
        "expireHour": 24,
        "emailType":"REQUEST_IDV, NOTIFY_IDV_SUCCESS, NOTIFY_IDV_FAILED"
    }
}
{
    "eventId":"uuid",
    "eventType":"KYB_QNRE_EMAIL",
    "data": {
        "email":"[email protected]",
        "otpCode":"",
        "url": "",
        "emailType":"OTP_VERIFICATION, FORM_NOTIFICATION, SUBMISSION_CONFIRMATION"
    }
}

Event Fields

FieldDescription
eventIdUnique event identifier
eventTypeEvent type
webhookIdWebhook configuration ID in ACMP
dataEvent payload

Event Types

eventTypeDescription
COMPLETEDFull verification process completed
SUBMIT_COMPLETEDData submission completed
BUSINESS_VERIFICATION_STATUSBusiness verification status (triggers on change)
AML_OGS_UPDATEThis event notifies about ongoing screening updates.
OCR_COMPLETEDTriggered when OCR processing is fully completed, providing the extracted document data for real-time access.
CASE_MANUAL_CORRECTEDTriggered after a case has been manually reviewed and corrected by an agent, syncing the final verified result.
KYB_IDV_EMAILTriggered when an IDV (Identity Verification) email needs to be sent during the KYB process, providing email details for custom delivery handling.
KYB_QNRE_EMAILTriggered when a questionnaire-related email needs to be sent, including OTP verification codes, form notification links, or submission confirmations.