Skip to content

Guides

Authentication

Every call is signed with HMAC-SHA256 in HTTP headers. No security value (key, signature, uid, mid) travels in the URL: if it shows up in the query string, the API answers 400 credentials_in_url.

Headers

HeaderValue
X-Api-Key-IdYour Key ID (your merchant panel → API).
X-Api-TimestampCurrent time in Unix seconds. ±300 s accepted: keep your clock in sync (NTP).
X-Api-NonceA new random value on every call (16 to 64 characters A-Z a-z 0-9 _ -). A repeated nonce is rejected (401 replayed_request).
X-Api-SignatureThe signature, hex encoded (64 characters).

What gets signed

Join these five values with dots and compute HMAC-SHA256 with your secret:

Canonical string
<timestamp>.<nonce>.<MÉTODO>.<ruta con query>.<sha256 hex del cuerpo>
  • METHOD in uppercase: GET, POST.
  • The path exactly as you send it, with the query: /v2/payments?ucode=A-1&limit=10.
  • The hash of the exact body you send (same bytes). No body (GET): the hash of the empty string, e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

Examples

PHP
<?php
$keyId  = getenv('EPAYOUTS_KEY_ID');
$secret = getenv('EPAYOUTS_SECRET');
$base   = 'https://api.e-payouts.com';

function epayouts(string $method, string $path, ?array $payload = null): array
{
    global $keyId, $secret, $base;
    $body  = $payload === null ? '' : json_encode($payload, JSON_UNESCAPED_SLASHES);
    $ts    = (string) time();
    $nonce = bin2hex(random_bytes(16));
    $sig   = hash_hmac('sha256', "$ts.$nonce.$method.$path." . hash('sha256', $body), $secret);

    $ch = curl_init($base . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POSTFIELDS     => $body === '' ? null : $body,
        CURLOPT_HTTPHEADER     => [
            "X-Api-Key-Id: $keyId",
            "X-Api-Timestamp: $ts",
            "X-Api-Nonce: $nonce",
            "X-Api-Signature: $sig",
            'Content-Type: application/json',
        ],
    ]);
    $response = json_decode((string) curl_exec($ch), true);
    return ['status' => curl_getinfo($ch, CURLINFO_HTTP_CODE), 'body' => $response];
}

$r = epayouts('POST', '/v2/payments', [
    'module' => '65cdef71-b6b8-11f1-8ee3-d721203bc734',
    'amount' => '25.00',
    'currency' => 'EUR',
    'ucode' => 'PEDIDO-1001',
]);
header('Location: ' . $r['body']['data']['checkout_url']);

Authentication errors

  • 401 missing_credentials · invalid_credentials — a header is missing or malformed.
  • 401 timestamp_out_of_range — your clock is off by more than 5 minutes.
  • 401 invalid_signature — the signature does not match (check the exact path, query and body) or the key does not exist or was regenerated.
  • 401 replayed_request — you reused a nonce.
  • 403 ip_not_allowed — the module has an IP allowlist and yours is not on it (your merchant panel → Sites → module).
  • 429 rate_limited — 20 authentication failures per minute from one IP block it for a minute.
If your key predates api2 it shows in your merchant panel as "Regenerate to use api2": regenerate the secret to be able to sign.