Autenticación
Cada llamada va firmada con HMAC-SHA256 en cabeceras HTTP. Ningún valor de seguridad (clave, firma, uid, mid) viaja en la URL: si aparece en la query, la API responde 400 credentials_in_url.
Cabeceras
| Cabecera | Valor |
|---|---|
| X-Api-Key-Id | Tu Key ID (tu panel de comercio → API). |
| X-Api-Timestamp | Hora actual en segundos Unix. Se aceptan ±300 s: mantené el reloj sincronizado (NTP). |
| X-Api-Nonce | Valor aleatorio nuevo en cada llamada (16 a 64 caracteres A-Z a-z 0-9 _ -). Un nonce repetido se rechaza (401 replayed_request). |
| X-Api-Signature | La firma, en hexadecimal (64 caracteres). |
Qué se firma
Unís con puntos estos cinco valores y calculás el HMAC-SHA256 con tu secreto:
<timestamp>.<nonce>.<MÉTODO>.<ruta con query>.<sha256 hex del cuerpo>- MÉTODO en mayúsculas: GET, POST.
- La ruta exactamente como la enviás, con la query: /v2/payments?ucode=A-1&limit=10.
- El hash del cuerpo exacto que enviás (los mismos bytes). Sin cuerpo (GET): el hash de la cadena vacía, e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.
Ejemplos
<?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']);import crypto from 'node:crypto';
const keyId = process.env.EPAYOUTS_KEY_ID;
const secret = process.env.EPAYOUTS_SECRET;
const base = 'https://api.e-payouts.com';
async function epayouts(method, path, payload) {
const body = payload === undefined ? '' : JSON.stringify(payload);
const ts = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(16).toString('hex');
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const signature = crypto.createHmac('sha256', secret)
.update(`${ts}.${nonce}.${method}.${path}.${bodyHash}`).digest('hex');
const res = await fetch(base + path, {
method,
body: body || undefined,
headers: {
'X-Api-Key-Id': keyId,
'X-Api-Timestamp': ts,
'X-Api-Nonce': nonce,
'X-Api-Signature': signature,
'Content-Type': 'application/json',
},
});
return { status: res.status, body: await res.json() };
}
const r = await epayouts('GET', '/v2/payments?ucode=PEDIDO-1001');
console.log(r.body.data);import hashlib, hmac, json, os, secrets, time
import requests
KEY_ID = os.environ["EPAYOUTS_KEY_ID"]
SECRET = os.environ["EPAYOUTS_SECRET"].encode()
BASE = "https://api.e-payouts.com"
def epayouts(method, path, payload=None):
body = "" if payload is None else json.dumps(payload, separators=(",", ":"))
ts = str(int(time.time()))
nonce = secrets.token_hex(16)
body_hash = hashlib.sha256(body.encode()).hexdigest()
canonical = f"{ts}.{nonce}.{method}.{path}.{body_hash}".encode()
signature = hmac.new(SECRET, canonical, hashlib.sha256).hexdigest()
headers = {
"X-Api-Key-Id": KEY_ID,
"X-Api-Timestamp": ts,
"X-Api-Nonce": nonce,
"X-Api-Signature": signature,
"Content-Type": "application/json",
}
r = requests.request(method, BASE + path, data=body.encode() or None, headers=headers, timeout=15)
return r.status_code, r.json()
print(epayouts("GET", "/v2/balance"))KEY_ID="…"; SECRET="…"
PATH_Q="/v2/payments"
BODY='{"module":"65cdef71-b6b8-11f1-8ee3-d721203bc734","amount":"25.00","ucode":"PEDIDO-1001"}'
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -r | cut -d' ' -f1)
SIG=$(printf '%s' "$TS.$NONCE.POST.$PATH_Q.$BODY_HASH" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)
curl -sS -X POST "https://api.e-payouts.com$PATH_Q" \
-H "X-Api-Key-Id: $KEY_ID" -H "X-Api-Timestamp: $TS" \
-H "X-Api-Nonce: $NONCE" -H "X-Api-Signature: $SIG" \
-H "Content-Type: application/json" --data "$BODY"Errores de autenticación
401 missing_credentials·invalid_credentials— falta una cabecera o tiene formato inválido.401 timestamp_out_of_range— tu reloj está desfasado más de 5 minutos.401 invalid_signature— la firma no coincide (revisá ruta, query y cuerpo exactos) o la clave no existe o fue regenerada.401 replayed_request— reusaste un nonce.403 ip_not_allowed— el módulo tiene lista de IPs permitidas y la tuya no está (tu panel de comercio → Sitios → módulo).429 rate_limited— 20 fallos de autenticación por minuto desde una IP la bloquean un minuto.
Si tu clave es anterior a api2 aparece en tu panel de comercio como "Regenerar para usar api2": regenerá el secreto para poder firmar.