S
docs.syncra.money

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 nameValue typeDescription
X-Merchant-TokenString (64-char hex)A unique public merchant identification token. Issued in the merchant cabinet.
X-SignatureStringThe 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:

  1. Get the current timestamp: Take the current time as a Unix timestamp (in seconds), for example: 1783856120.
  2. 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 "".
  3. Build the payload: Join the timestamp from step 1 and the raw body from step 2 with a dot (.). The formula: payload = timestamp + "." + raw_body Example: 1783856120.{"amount":150000,"currency":"RUB"}
  4. 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.
  5. Convert to a hex string: Convert the binary hashing output to a lowercase hexadecimal string.
  6. 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:

ParameterLegacy system V1New system V2
Hashing algorithmHMAC-SHA512HMAC-SHA256
Signature input formatDifferent strings per endpoint (for example, ts;payin_id;salt)A single timestamp.body format for all requests
Merchant identifierMERCHANT header (GUID)X-Merchant-Token (64-char hex)
Signature transportSIGNATURE header (raw hex)X-Signature header (t={ts},v1={hex})
Replay attack protectionGateway-side: a timestamp older than 300 s or more than 60 s in the future → rejectionBuilt into the gateway (window: 5 minutes back / 60 seconds forward)
Time headerCarried inside the signature formulaInside the signature (t= in X-Signature); no separate header required

On this page