Developer documentation

Verify payment callback signatures

Verify payment webhooks with HMAC-SHA256 over the timestamp and raw request body. Check timestamp freshness, validate the order and prevent duplicate fulfillment before returning HTTP 2xx.

These order APIs (create, query and resend) use HMAC-SHA256 signatures. GET /api/v1/ping requires only an API Key. Payment callbacks also include a signature. HMAC uses your merchant Webhook secret, not your API Key.

01. Header Specifications

01. Sending order API requests

Client to Server

Create, query and resend requests also require X-Bolt-Key. The timestamp must be within 5 minutes of the server clock. Generate a new timestamp and signature when retrying.

Unix millisecond timestampRequired
X-Bolt-Timestamp
HMAC-SHA256 hexadecimal signatureRequired
X-Bolt-Signature

02. Receiving payment callbacks

Server to Merchant

BoltUtil generates callback timestamps and signatures. Receivers should verify the raw body, check the timestamp window and handle events and business orders idempotently.

Unix millisecond timestampRequired
X-Bolt-Webhook-Timestamp
HMAC-SHA256 hexadecimal signatureRequired
X-Bolt-Webhook-Signature

02. Signature Calculation

1

Construct the signed content

Join the header timestamp, a dot and the exact raw body: timestamp + "." + raw_body. Encode as UTF-8. Keep the dot even for an empty body.

// 1. Construct payload: timestamp + "." + body
String payload = timestamp + "." + body;
2

Calculate HMAC-SHA256

Use the secret from your merchant Webhook configuration. The body used for signing must exactly match the body sent in your API request.

// JDK standard library: javax.crypto.Mac and SecretKeySpec
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
3

Encode as lowercase hexadecimal

Encode the 32-byte digest as 64 lowercase hexadecimal characters. Send it as X-Bolt-Signature for API requests. Verify callbacks against X-Bolt-Webhook-Signature using constant-time comparison.

// JDK 17+: java.util.HexFormat
String signature = HexFormat.of().formatHex(hash);

03. Verification Notes

  • Do not parse and reserialize JSON before verification. Whitespace, line breaks, key order and encoding all affect the signature.
  • HMAC verifies content integrity but does not prevent replay by itself. Check timestamp freshness and deduplicate events.
  • A valid signature does not mean fulfillment is complete. Check order association, payment status, amounts, currency and settlement type.
View callback handling →
signature / cURL
# Calculate a request signature without sending a request.
# BOLTUTIL_WEBHOOK_SECRET is your merchant Webhook secret, not API Key.
: "${BOLTUTIL_WEBHOOK_SECRET:?Set your Webhook secret}"
timestamp=$(python3 -c 'import time; print(int(time.time()*1000))')
body='{"externalOrderId":"INV-20260209-99281"}'
signature=$(printf '%s.%s' "$timestamp" "$body" | openssl dgst -sha256 -hmac "$BOLTUTIL_WEBHOOK_SECRET" | awk '{print $NF}')
printf 'X-Bolt-Timestamp: %s\nX-Bolt-Signature: %s\n' "$timestamp" "$signature"
# Send this SAME body with the above headers and X-Bolt-Key.
# Callback header names are X-Bolt-Webhook-Timestamp and
# X-Bolt-Webhook-Signature. Verify raw bytes before parsing JSON.

Addresses, transaction hashes and orders are examples. Replace them with your own test data.

Base URL

PROD
https://api.boltutil.com