Authentication
Authenticating requests to the Syncra Merchant API V2 with HMAC-SHA256 signatures
Authenticating requests
Security is the top priority when interacting with the Syncra Merchant API V2. Every request to our API must pass mandatory authentication and data integrity verification. This protects your integration against man-in-the-middle (MITM) attacks, parameter tampering, and replay attacks.
Syncra V2 uses an authentication scheme based on secret tokens and HMAC-SHA256 cryptographic signatures.
Required HTTP headers
Every HTTP request to our API must carry two required headers:
| Header name | Value type | Description |
|---|---|---|
X-Merchant-Token | String (64-char hex) | A unique public merchant identification token. Issued in the merchant cabinet. |
X-Signature | String | The computed cryptographic signature of the request. Format: t={timestamp},v1={hmac_sha256_hex}. The time travels inside the signature (the t= field), and the server validates it from there. |
You do not need to send a separate X-Timestamp header: server time is
compared against the t= stamp from X-Signature. If your integration
already sends X-Timestamp, that is not an error — the extra header is
simply ignored.
Note: unlike the legacy API V1, where the headers were named
MERCHANT and SIGNATURE without developer prefixes, API V2 uses only
headers with the X- prefix. Signatures are computed with SHA256 instead
of SHA512.
Computing the signature
To compute the signature, you need your private Webhook Secret Key (also called the signing secret), which is issued once in the merchant control panel.
To generate a valid signature for the X-Signature header, follow these
steps:
- Get the current timestamp: Take the current time as a Unix timestamp
(in seconds), for example:
1783856120. - Prepare the request body: Use the original raw JSON request body
(Raw Request Body) exactly as it travels over the network. Whitespace,
line breaks, and field order all matter. For requests without a body (a
GET, for example), use an empty string
"". - Build the payload: Join the timestamp from step 1 and the raw body
from step 2 with a dot (
.). The formula:payload = timestamp + "." + raw_bodyExample:1783856120.{"amount":150000,"currency":"RUB"} - Compute HMAC-SHA256: Using your private secret key (Webhook Secret Key) as the signing key, compute HMAC-SHA256 over the payload built in step 3.
- Convert to a hex string: Convert the binary hashing output to a lowercase hexadecimal string.
- Assemble the final header value: Build the string in the format:
X-Signature: t={timestamp},v1={hex_signature}
Replay attack protection: Syncra validates the timestamp from the
t= field of the X-Signature header against the server's system time.
A signature older than 5 minutes or more than 60 seconds in the
future is rejected with 401 Unauthenticated (keep your clocks
NTP-synced). This prevents attackers from intercepting and re-sending
legitimate requests.
Implementation examples
Below are copy-ready code snippets in popular programming languages for generating signatures.
const crypto = require('crypto');
function generateV2Signature(body, webhookSecret) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const payload = `${timestamp}.${body}`;
const signature = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex');
return {
'X-Merchant-Token': 'your_merchant_token_here',
'X-Signature': `t=${timestamp},v1=${signature}`
};
}import time
import hmac
import hashlib
def generate_v2_signature(body_str: str, webhook_secret: str):
timestamp = str(int(time.time()))
payload = f"{timestamp}.{body_str}"
signature = hmac.new(
webhook_secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return {
'X-Merchant-Token': 'your_merchant_token_here',
'X-Signature': f"t={timestamp},v1={signature}"
}<?php
function generateV2Signature($bodyStr, $webhookSecret) {
$timestamp = (string)time();
$payload = $timestamp . '.' . $bodyStr;
$signature = hash_hmac('sha256', $payload, $webhookSecret);
return [
'X-Merchant-Token' => 'your_merchant_token_here',
'X-Signature' => "t={$timestamp},v1={$signature}"
];
}
?>package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"time"
)
func GenerateV2Signature(body []byte, secret []byte) map[string]string {
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
payload := fmt.Sprintf("%s.%s", timestamp, string(body))
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(payload))
signature := hex.EncodeToString(mac.Sum(nil))
return map[string]string{
"X-Merchant-Token": "your_merchant_token_here",
"X-Signature": fmt.Sprintf("t=%s,v1=%s", timestamp, signature),
}
}API security comparison (V1 vs V2)
For developers migrating from the legacy V1 system, here is a table of the differences:
| Parameter | Legacy system V1 | New system V2 |
|---|---|---|
| Hashing algorithm | HMAC-SHA512 | HMAC-SHA256 |
| Signature input format | Different strings per endpoint (for example, ts;payin_id;salt) | A single timestamp.body format for all requests |
| Merchant identifier | MERCHANT header (GUID) | X-Merchant-Token (64-char hex) |
| Signature transport | SIGNATURE header (raw hex) | X-Signature header (t={ts},v1={hex}) |
| Replay attack protection | Gateway-side: a timestamp older than 300 s or more than 60 s in the future → rejection | Built into the gateway (window: 5 minutes back / 60 seconds forward) |
| Time header | Carried inside the signature formula | Inside the signature (t= in X-Signature); no separate header required |