Sign in
Introducción/Autenticación

Autenticación y firma de solicitudes

Firma las solicitudes a la API con HMAC-SHA256 usando el UUID de tu proyecto y tu API key.

Toda solicitud a la API (excepto los webhooks entrantes) debe incluir el UUID de tu proyecto y una firma de la solicitud. La firma demuestra que la solicitud proviene de ti y que nadie la modificó en el camino.

API keys

2328.io utiliza dos claves que comparten el mismo algoritmo de firma pero cubren distintos endpoints:

ClaveSe utiliza para
API keyPagos, monederos estáticos, saldo, tipos de cambio y verificación de webhooks de pago / monedero estático
Payout API keyTodos los endpoints /v1/payout/* y la verificación de webhooks de retiro

Ambas claves se gestionan en la configuración de tu proyecto en 2328.io. Los ejemplos siguientes mencionan "API key" de forma genérica — sustitúyela por la correcta según el endpoint que llames.

Nunca mezcles las dos claves: firmar una solicitud de retiro con la API key habitual (o una solicitud de pago con la clave de retiro) devuelve un error de firma.

Headers requeridos

HeaderTipoRequeridoDescripción
Content-TypestringSiempre application/json
projectstringUUID de tu proyecto
signstringFirma HMAC-SHA256 de la solicitud, calculada con tu API key
User-AgentstringIdentifica tu aplicación (p. ej. MyShop/1.4 (+https://myshop.example)). Las solicitudes sin User-Agent pueden ser bloqueadas.

Cómo funciona la firma

Piensa en la firma como una huella del cuerpo de la solicitud. Se construye así:

  1. Serializa el cuerpo a JSON (compacto — sin espacios en blanco adicionales).
  2. Codifica ese JSON en Base64. Este paso normaliza la entrada entre lenguajes — una vez convertido a ASCII plano, todos los lenguajes producen los mismos bytes para HMAC.
  3. Calcula HMAC-SHA256 sobre la cadena Base64 usando tu API key, y luego convierte el resultado a hexadecimal en minúsculas.

Para GET y otros tipos de solicitud sin cuerpo, firma una cadena vacía en lugar del JSON.

La firma de la cadena vacía es constante para una API key dada. Puedes cachearla si haces muchas llamadas GET.

Implementaciones

PHP
<?php
function apiSign(array $data, string $apiKey): string {
    $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $base64 = base64_encode($json);
    return hash_hmac('sha256', $base64, $apiKey);
}

Solicitudes sin cuerpo (GET)

Para solicitudes con cuerpo vacío (por ejemplo GET /v1/payout/status/{uuid}), firma una cadena vacía:

Shell
SIGN=$(printf '' | openssl dgst -sha256 -hmac "$API_KEY" -hex | awk '{print $NF}')

Ejemplo completo de solicitud

Shell
curl -X POST https://api.2328.io/api/v1/payment \
  -H "Content-Type: application/json" \
  -H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
  -H "project: YOUR_PROJECT_UUID" \
  -H "sign: YOUR_HMAC_SIGNATURE" \
  -d '{"amount":"100.00","currency":"USD","order_id":"ORDER-123"}'

Nunca expongas tu API key en código del lado del cliente. Firma las solicitudes en tu backend. Una API key filtrada le da a cualquiera acceso completo a tu cuenta de comerciante.

Verificación de firmas de webhook

Cuando 2328.io te envía un webhook, el mismo algoritmo se aplica a la inversa:

  1. Extrae el campo sign del payload.
  2. Codifica los campos restantes a JSON (compacto, sin espacios en blanco).
  3. Codifica esa cadena en Base64.
  4. Calcula HMAC-SHA256 con la clave correspondiente.
  5. Compáralo con el sign recibido usando una comparación de tiempo constante (hash_equals, crypto.timingSafeEqual, hmac.compare_digest, subtle.ConstantTimeCompare, OpenSSL.fixed_length_secure_compare).

La clave de firma depende del origen del webhook:

WebhookClave para verificar
Webhooks de pago / monedero estático (/v1/payment, /v1/static-wallet)API key
Webhooks de retiro (/v1/payout)Payout API key

Errores comunes de verificación. Tu codificador JSON debe producir exactamente los mismos bytes que produjo el emisor — de lo contrario el Base64 difiere y la firma no coincidirá.

  • Go: usa json.NewEncoder con SetEscapeHTML(false). El json.Marshal por defecto escapa <, >, & a < y rompe la firma.
  • Python: pasa ensure_ascii=False a json.dumps. Sin esto, los caracteres no ASCII (cirílico, chino, …) se escapan a \uXXXX.
  • JSON compacto: sin espacios en blanco entre campos (separators=(",", ":") en Python).
  • Orden de campos (Go): un map[string]any simple aleatoriza las claves al recodificar. Usa json.RawMessage, una struct ordenada o elimina sign de los bytes originales.

Si la verificación sigue fallando, ejecuta apiSign sobre el payload tú mismo — debe producir la misma cadena hexadecimal que el sign recibido.

Una firma válida no previene reenvíos. Solo prueba que el webhook vino de 2328.io — no impide que un atacante vuelva a publicar un webhook capturado más tarde. Comprueba siempre la idempotencia por uuid (o txid para wallets estáticos) antes de acreditar fondos. Rechaza con HTTP 401 si la firma falta o es incorrecta.

Los ejemplos de código completos están en Webhook Notifications. El manejo de reintentos y las reglas de idempotencia están en Buenas prácticas.