Authentication & Signature
Pay-in and refund requests are authenticated with two HTTP headers:
| Header | Description |
|---|---|
Sdk-Partner-Token |
Partner credentials in the form service_id:secret. |
X-Signature |
HMAC-SHA256 of the compact (canonicalized) JSON request body, keyed with your Sign Key, hex-encoded. |
Sign the body you actually send. Before verifying, the server parses the received body and re-serializes it as compact JSON — no insignificant whitespace, UTF-8 left unescaped — keeping the fields in the order they arrived. So:
- sign the same field order you transmit; sorting the keys produces a different signature;
- keep every field you send in the signed payload,
nullvalues included; - send the digest as lowercase hex, the way
HMAC-SHA256hex output comes out.
A signature computed over pretty-printed bytes will not match.
GET requests carry no body and require only Sdk-Partner-Token.
Successful responses carry the same header: X-Signature holds the HMAC-SHA256 of the JSON data object, so you can verify authenticity. Error responses can arrive without it — an authentication failure is rejected before the signing key is resolved. Webhooks are signed as described in Webhook signature.
Credentials are issued per environment — see Sandbox & Testing.
Webhook signature
Each webhook includes an X-Signature header: the lowercase hex HMAC-SHA256 (64 characters) of the raw request body, keyed with your Sign Key. The hash covers the whole body exactly as received — data and eventId included.
Important: Hash the raw body before JSON parsing. A body that is parsed and serialized again can differ in key order, spacing or escaping, and verification fails.
For the body {"data":{"amount":"10.00000000","id":"28a3c125a28075453"},"eventId":"3f9a1b2c4d5e6f78a0"} the HMAC input is this whole string, byte for byte.
Code Examples
JavaScript
const crypto = require('crypto');
// rawBody: the request body as received, before JSON parsing
function verifyWebhookSignature(rawBody, signature, signKey) {
const calculated = crypto.createHmac('sha256', signKey).update(rawBody).digest('hex');
return calculated.length === signature.length &&
crypto.timingSafeEqual(Buffer.from(calculated), Buffer.from(signature));
}
Python
import hmac
import hashlib
# raw_body: the request body bytes as received, before JSON parsing
def verify_webhook_signature(raw_body: bytes, signature: str, sign_key: str) -> bool:
calculated = hmac.new(sign_key.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(calculated, signature)
Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
// rawBody: the request body bytes as received, before JSON parsing
func verifyWebhookSignature(rawBody []byte, signature, signKey string) bool {
h := hmac.New(sha256.New, []byte(signKey))
h.Write(rawBody)
return hmac.Equal([]byte(hex.EncodeToString(h.Sum(nil))), []byte(signature))
}
Try It: Webhook Signature Calculator
Paste the raw webhook body exactly as received — without added spaces or line breaks — and your Sign Key to verify your implementation.
Note: The hash is computed locally using your browser.