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, null values included;
  • send the digest as lowercase hex, the way HMAC-SHA256 hex 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.