Check Webhook Signature

Prerequisites

Please acquire Webhook Signature Secret which is needed to compute the signature and compare to the one Xenith sent from webhook. Refer here to check your webhook signature secret in the platform. Feel free to reach out to us at [email protected] if you have any inquiries.

Xenith signs all webhook events by including a signature in the request headers. This allows you to verify that the events were sent by Xenith. Follow these steps to verify the signature:

  1. Construct the string to be signed by concatenating the following elements separated by \n character: {HTTP_METHOD}\n{URL_PATH}\n{REQUEST_BODY}\n{TIMESTAMP_RFC3339}
    • HTTP_METHOD: POST, all in uppercase
    • URL_PATH: The full path of the URL
    • REQUEST_BODY: The full body of the request
    • TIMESTAMP_RFC3339: The timestamp from X-Xenith-Timestamp header in RFC3339 format (e.g., 2026-07-16T12:34:56.123456789Z)
  2. Create an HMAC using SHA256 hash from the concatenated string as the message and your Webhook Signature Secret as the secret key
  3. Encode the resulting hash in Base64
  4. Verify the generated signature with the X-Xenith-Signature header from the webhook request is the same with computed signature by your system
  5. Ensure the timestamp in the X-Xenith-Timestamp header is within an acceptable time range to prevent replay attacks

The actual signature will vary based on your specific inputs. The secret used for signature verification should be securely stored on your server and will not be transmitted with the webhook payload.

Request Header Parameters

Header KeyExample Value
Content-Typeapplication/json
X-Xenith-Timestamp2026-07-16T12:34:56.123456789Z
X-Xenith-SignatureComputed Signature

Try the Webhook Signature Simulator

Webhook Signature Simulator

Reproduce the signature Xenith sends with a webhook. All calculation happens in this browser.

Signature result

String to sign

POST\n/webhooks/example\n{"data":{"currency":"IDR","paymentChannels":["MDR.VA","BNI.VA"],"paymentMethod":"VIRTUAL_ACCOUNT","status":"INACTIVE"},"eventType":"maintenance.started.payins","schemaVersion":"1.0.1","timestamp":"2026-07-16T12:34:56.123456789Z"}\n2026-07-16T12:34:56.123456789Z

Base64 HMAC-SHA256

Enter a secret to compute the signature.
The separators are literal backslash + n bytes (0x5c 0x6e), not line-feed bytes.

Compare a received signature

Example
Secrettest-secret
MethodPOST
Path/webhooks/example
Body{"data":{"currency":"IDR","paymentChannels":["MDR.VA","BNI.VA"],"paymentMethod":"VIRTUAL_ACCOUNT","status":"INACTIVE"},"eventType":"maintenance.started.payins","schemaVersion":"1.0.1","timestamp":"2026-07-16T12:34:56.123456789Z"}
Timestamp2026-07-16T12:34:56.123456789Z
ExpectedBPKtUYIQkxWJDOG5JepCkCzTpw9L/8akql305fjfrRE=

Reference implementations

<?php
$method = 'POST';
$path = '/webhooks/example'; // Path only: no host or query string
$rawBody = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_XENITH_TIMESTAMP'];
$secret = getenv('XENITH_WEBHOOK_SECRET');

// Single-quoted '\n' is a literal backslash followed by n in PHP.
$stringToSign = $method . '\n' . $path . '\n' . $rawBody . '\n' . $timestamp;
$computed = base64_encode(hash_hmac('sha256', $stringToSign, $secret, true));

if ($computed === $_SERVER['HTTP_X_XENITH_SIGNATURE']) {
    echo 'Signature is valid';
}