Ir al contenido
Desarrolladores

Integración en minutos

Un único SDK de PHP sin dependencias: crea facturas, redirige a la página de pago y verifica los webhooks firmados. Para cualquier otro lenguaje está REST.

La API de un vistazo
  • URL baseapi.payora.money
  • AutenticaciónHMAC-SHA256
  • FormatoJSON · HTTPS
  • SDKPHP · Node.js · Python
  • EspecificaciónOpenAPI 3.0
inicio rápido

De la factura al webhook

  1. 01

    Crea una factura

    Una llamada a la API con un ID de pedido y un importe en fíat o cripto devuelve una URL de pago alojada.

  2. 02

    El cliente paga

    Elige una moneda, escanea el QR o abre su cartera, y el checkout confirma en vivo al finalizar.

  3. 03

    Recibes un webhook firmado

    Verifica la firma HMAC, acredita el pedido una sola vez y mira cómo el pago llega a tu saldo.

SDK de PHP

Un archivo, sin dependencias. createInvoice(), getInvoice() y verifyWebhook(): se copia en cualquier proyecto.

Autenticación HMAC

Las peticiones y los webhooks van firmados con HMAC-SHA256, con margen de tiempo y claves de idempotencia.

API REST

JSON sencillo sobre HTTPS: intégralo desde cualquier tecnología, no solo desde PHP.

checkout
<?php
require 'Payora.php';
$payora = new Payora('https://api.payora.money', getenv('PAYORA_KEY'), getenv('PAYORA_SECRET'));

// 1 — create an invoice (fiat-priced; the payer picks a coin)
$inv = $payora->createInvoice([
  'order_id'   => 'ORDER-42',
  'mode'       => 'fiat',
  'amount'     => '9.99',
  'return_url' => 'https://shop.example/thanks',
  'lang'       => 'en',
], 'ORDER-42'); // Idempotency-Key: a retry never creates a second invoice
header('Location: ' . $inv['pay_url']);

// 2 — webhook endpoint: ALWAYS verify the signature first
$event = $payora->verifyWebhook(file_get_contents('php://input'), getallheaders());
if ($event === null) { http_response_code(400); exit; }
if ($event['status'] === 'paid') {
  creditOrderOnce($event['id'], $event['order_id'], $event['amount'], $event['currency']);
}
http_response_code(200); // 2xx stops retries

Consigue tu API key y secret creando una cuenta. El SDK completo está en tu panel.

autenticación

Autenticación

Cada llamada, salvo /v1/health, lleva tres cabeceras firmadas. La firma demuestra que la solicitud viene de quien tiene tu api_secret y que no se modificó por el camino.

CabeceraValorDescripción
X-Payora-Keypk_…Tu api_key pública. Identifica la tienda.
X-Payora-Timestampunix secondsEl momento en que firmaste la solicitud, en segundos Unix.
X-Payora-Signaturehex · 64HMAC-SHA256 de timestamp + "." + cuerpo sin procesar con tu api_secret como clave, en hex en minúsculas.
X-Payora-Test1Marca la factura como una prueba de tu integración: es real y puede recibir monedas, pero no entra en los informes de demanda y conversión.
Idempotency-KeystringOpcional en POST /v1/invoice y POST /v1/payout. Un reintento con la misma clave devuelve el primer resultado en lugar de crear un duplicado; la misma clave con otro cuerpo responde 409.
Content-Typeapplication/jsonObligatorio en las solicitudes con cuerpo.
X-Payora-Signature=hex( HMAC-SHA256( api_secret, timestamp + "." + raw_body ) )

Firma exactamente los bytes que envías. Volver a codificar el JSON después de firmar —aunque solo cambie el orden de las claves o sobre un espacio— rompe la firma.

En una solicitud GET el cuerpo está vacío, así que la cadena firmada es el timestamp seguido de un punto.

La API rechaza un timestamp que se aleje más de 120 segundos de su reloj. Mantén tu servidor sincronizado con NTP.

Guarda api_secret en tu servidor. Firma tanto las solicitudes como los webhooks: quien lo tenga puede crear facturas en tu nombre y falsificar notificaciones.

sign-request
<?php
$body = json_encode(['order_id' => 'ORDER-42', 'mode' => 'fiat', 'amount' => '9.99'], JSON_UNESCAPED_SLASHES);
$ts   = (string) time();
$sig  = hash_hmac('sha256', $ts . '.' . $body, $apiSecret); // lowercase hex

$ch = curl_init('https://api.payora.money/v1/invoice');
curl_setopt_array($ch, [
  CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'X-Payora-Key: ' . $apiKey,
    'X-Payora-Timestamp: ' . $ts,
    'X-Payora-Signature: ' . $sig,
    'Idempotency-Key: ORDER-42',
    'Content-Type: application/json',
  ],
]);
$invoice = json_decode(curl_exec($ch), true);
POST /v1/invoice

Parámetros de la factura

Todo opcional excepto order_id y amount. El modo fiat permite al pagador elegir cualquier moneda habilitada en tu cuenta; el modo crypto también necesita currency.

ParámetroTipoDescripción
order_idstring · requiredTu referencia de pedido única (≤190 caracteres). Una repetición se rechaza con 409: a salvo de envíos duplicados.
amountstring · requiredImporte en fiat (mode=fiat) o importe cripto exacto (mode=crypto).
modefiat | cryptoPor defecto fiat: la tasa se fija por moneda en el checkout. crypto fija la moneda y el importe de antemano.
fiat_currencystringCódigo ISO de fiat para el modo fiat (default USD).
currencystringMoneda para el modo crypto (p. ej., TON, TRX, USDT_TRON).
return_urlhttps URLA dónde devuelve al cliente el botón «Volver a la tienda» del checkout: a tu sitio. Solo http(s) absoluto.
langen·ru·uk·es·deIdioma del checkout, para que un cliente de tu tienda rusa vea el checkout en ruso, no en inglés. También configurable como ?lang=ru en la URL de pago.
ttlsecondsCuánto tiempo se puede pagar la factura (120–86400 segundos, 1800 por defecto).
customer_emailemailOpcional: aparece en el recibo y se envía por correo al pagador.
notesstringNota de formato libre almacenada en la factura.

return_url y lang también funcionan como parámetros de consulta añadidos a la pay_url que devuelve la API — <pay_url>&lang=ru&return_url=https://shop.example/thanks. Envía siempre al comprador a la pay_url devuelta por la API: lleva un token de acceso único por factura, por lo que no se puede acceder a una página de checkout adivinando números de factura. No reconstruyas el enlace a partir del id de la factura.

Generador de solicitudes

Crea una solicitud de factura firmada

Rellena los campos y copia un comando cURL listo. Se firma solo en tu terminal con $PAYORA_KEY y $PAYORA_SECRET: esta página nunca ve tus claves y no envía nada.

mode
POST /v1/invoice
BODY='{"order_id":"ORDER-42","mode":"fiat","amount":"9.99","fiat_currency":"USD","return_url":"https://shop.example/thanks","lang":"es","ttl":1800}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$PAYORA_SECRET" | cut -d' ' -f2)

curl -X POST https://api.payora.money/v1/invoice \
  -H "X-Payora-Key: $PAYORA_KEY" \
  -H "X-Payora-Timestamp: $TS" \
  -H "X-Payora-Signature: $SIG" \
  -H 'Idempotency-Key: ORDER-42' \
  -H 'Content-Type: application/json' \
  -d "$BODY"

Desde esta página no se envía nada.

webhooks

Webhooks

Cuando una factura o un pago cambia de estado, Payora envía por POST un evento JSON firmado a tu URL de callback. Verifica primero la firma, abona el pedido una sola vez y responde 2xx.

EventostatusCuándo se envía
invoice.paidpaidEl pago está confirmado en la cadena. Incluye el importe, la moneda y tx_hash.
invoice.underpaidunderpaidLa factura venció tras recibir menos del importe esperado.
invoice.expiredexpiredLa factura venció sin recibir nada.
invoice.cancelledcancelledLa factura se canceló. Si ya había llegado dinero, el evento lo describe.
invoice.cancel_settled—El dinero que llegó a una factura cancelada ya está resuelto: abonado o devuelto.
invoice.late_payment—Llegó dinero a una factura después de cancelarla. Se envía una vez por cada ingreso así; result indica si se abonó o se está devolviendo.
payout.sentsentUn lote de pagos se firmó y se difundió. Incluye cada elemento con su tx_hash.

Cabeceras de cada entrega

CabeceraValorDescripción
X-Payora-IduuidId único del evento, igual que id en el cuerpo. Guárdalo para ignorar repeticiones.
X-Payora-Eventinvoice.paidNombre del evento.
X-Payora-Timestampunix secondsCuándo se firmó este intento.
X-Payora-Signaturehex · 64El mismo esquema que en las solicitudes: HMAC-SHA256 de timestamp + "." + cuerpo sin procesar con tu api_secret.

Contenido de invoice.paid

CampoTipoDescripción
iduuidId del evento para evitar duplicados.
eventstringNombre del evento.
invoice_idintNúmero de factura en Payora.
order_idstringTu referencia de pedido, tal como la enviaste al crear la factura.
statusstringpaid en invoice.paid.
currencystringMoneda con la que se pagó, p. ej. USDT_TRON.
amountdecimal stringImporte recibido.
amount_unitsinteger stringEl mismo importe en las unidades mínimas de la moneda: úsalo para comparaciones exactas.
expecteddecimal stringImporte que pedía la factura.
fiat_amountdecimal string | nullPrecio en fiat de la factura tal como la creaste; null en facturas en modo cripto.
fiat_currencystring | nullCódigo ISO de fiat_amount; null en facturas en modo cripto.
tx_hashstringHash de la transacción en la cadena.
paid_atunix secondsCuándo se creó el evento.
testbooltrue en facturas de prueba.
invoice.paid
{
    "id": "6f1c2a4e-8b0d-4c1e-9a57-2f3d9e1b7c40",
    "event": "invoice.paid",
    "invoice_id": 1042,
    "order_id": "ORDER-42",
    "status": "paid",
    "currency": "USDT_TRON",
    "amount": "9.99",
    "amount_units": "9990000",
    "expected": "9.99",
    "fiat_amount": "9.99",
    "fiat_currency": "USD",
    "tx_hash": "3f9a…c21e",
    "paid_at": 1758038400,
    "test": false
}

Si el importe recibido difiere de la factura, se añaden amount_received y amount_expected.

Entrega y reintentos

Cualquier respuesta distinta de 2xx, o ninguna en 15 segundos, se reintenta con espera exponencial de como máximo una hora entre intentos, hasta 12 intentos. Cada intento se firma de nuevo con otro timestamp; el cuerpo y el id del evento no cambian, así que deduplica por id.

Tras el último intento el evento se marca como fallido y el propietario de la tienda recibe un correo. Nunca se siguen redirecciones y la URL de callback debe ser accesible desde internet: nada de localhost ni direcciones privadas.

Antes de abonar un pedido

  • Lee el cuerpo sin procesar antes de analizar el JSON.
  • Recalcula la firma y compárala en tiempo constante: hash_equals, crypto.timingSafeEqual, hmac.compare_digest.
  • Rechaza timestamps fuera de tu margen: los SDK aceptan ±300 segundos.
  • Comprueba el estado y el importe con tu pedido y abona una sola vez por id de evento.
  • Responde 2xx rápido y haz el trabajo lento después de responder.
webhook
<?php
require 'Payora.php';
$payora = new Payora('https://api.payora.money', getenv('PAYORA_KEY'), getenv('PAYORA_SECRET'));

$raw   = file_get_contents('php://input');           // raw, before any parsing
$event = $payora->verifyWebhook($raw, getallheaders()); // HMAC + ±300 s window, constant-time
if ($event === null) { http_response_code(400); exit; } // bad / forged / stale

if ($event['event'] === 'invoice.paid' && !alreadyProcessed($event['id'])) {
  creditOrder($event['order_id'], $event['amount'], $event['currency'], $event['tx_hash']);
  rememberProcessed($event['id']);
}
http_response_code(200); // 2xx stops retries
Banco de pruebas

Banco de pruebas de firma de webhooks

Introduce un secreto, un timestamp y el cuerpo sin procesar para ver la firma exacta que enviaría Payora, o comprueba una que hayas recibido.

Funciona en tu navegador con WebCrypto. Nada de lo que escribas sale de esta página. En un ordenador compartido usa un secreto de prueba, no el real.

X-Payora-Timestamp
Cadena firmada
1758038400.{…}
Cabeceras que envía Payora
headers
X-Payora-Timestamp: 1758038400
X-Payora-Signature: …

Pega una firma para comparar

pagos masivos

API de pagos masivos

Envía cripto a hasta 500 carteras en una sola petición firmada e idempotente: nóminas, pagos a afiliados o retiradas. El total del lote más la comisión por línea se retiene al instante en tu saldo interno; después el Operador firma y difunde, y se dispara un webhook firmado payout.sent. La misma autenticación HMAC que todo lo demás.

payout
# create a payout batch — funds held instantly, sent by the operator
curl -X POST https://api.payora.money/v1/payout \
  -H 'X-Payora-Key: pk_…' -H 'X-Payora-Timestamp: 1700000000' \
  -H 'X-Payora-Signature: <hmac-sha256>' -H 'Idempotency-Key: payroll-2026-01' \
  -d '{"currency":"USDT_TRON","items":[
        {"address":"TR7…","amount":"250.00"},
        {"address":"TX9…","amount":"99.50","tag":"batch-A"}]}'

# → 201 Created
{ "batch_id": 812, "status": "pending", "currency": "USDT_TRON",
  "item_count": 2, "total": "349.50", "fee": "…", "items": [ … ] }

# poll status, or receive a signed payout.sent webhook when it is sent
curl https://api.payora.money/v1/payout/812 -H 'X-Payora-Key: …' …

La validación es estricta: cualquier dirección incorrecta o importe con precisión excesiva rechaza el lote completo (HTTP 422) con los índices de los elementos problemáticos: una nómina nunca descarta a un destinatario en silencio. Cancela un lote aún pendiente con POST /v1/payout/{id}/cancel para liberar la retención. Consulta el análisis de la arquitectura.

referencia

Referencia de endpoints

Cada endpoint habla JSON simple sobre HTTPS en api.payora.money. Todos excepto /v1/health usan la misma autenticación de solicitudes con HMAC-SHA256. La especificación legible por máquina está en openapi.json.

EndpointAccesoDescripción
GET/v1/healthno authEstado del servicio más la lista de monedas habilitadas.
POST/v1/invoiceauthCrea una factura (modo fiat o crypto). Admite Idempotency-Key. Devuelve 201 con invoice_id, status, pay_url y expires; el modo crypto añade la dirección, el importe y el deeplink.
GET/v1/invoice/{id}authEstado de la factura (solo facturas propias): status, remaining y charge — currency, dirección, importes esperados/recibidos y tx_hash.
POST/v1/payoutauth · Pro+Crea un lote de pagos masivos (validación estricta, fondos retenidos al instante). Admite Idempotency-Key. Devuelve 201 con la vista del lote: batch_id, status, totales, comisión, elementos.
GET/v1/payoutauthLista tus lotes de pagos recientes (?limit=, default 50).
GET/v1/payout/{id}authEstado y elementos del lote de pagos (solo lotes propios).
POST/v1/payout/{id}/cancelauthCancela un lote aún pendiente y libera el saldo retenido. Devuelve 409 si el lote ya no se puede cancelar.
descargas

SDK y descargas

Archivos únicos sin dependencias. Todos los SDK firman solicitudes y verifican webhooks del mismo modo.

phpPHPPayora.php

PHP 7.4+ con cURL, un solo archivo. createInvoice, getInvoice y verifyWebhook.

jsNode.jspayora.js

Node.js solo con https y crypto integrados. Facturas, saldo, pagos y verifyWebhook.

pyPythonpayora.py

Solo la biblioteca estándar de Python. Facturas, saldo, pagos y verify_webhook.

{ }Postmanpayora.postman_collection.json

Una colección de Postman que firma cada llamada por ti.

{…}OpenAPI 3.0openapi.json

La especificación legible por máquina de cada endpoint, para generadores de código y clientes de API.

api.payora.moneyAbrir
¿Usas WooCommerce, WHMCS, Magento u otro CMS?

Un módulo listo hace todo lo anterior por ti: factura, redirección y webhook verificado.

Ver plugins
Preguntas

Preguntas frecuentes

Respuestas directas, también a las incómodas: custodia, comisiones y qué pasa cuando algo sale mal.

Centro de ayuda

¿Cómo integro Payora?

Instala el SDK de PHP de un solo archivo (sin dependencias) o llama directamente a la API REST. Crea una factura con createInvoice(), redirige al cliente a su pay_url y gestiona un único evento de webhook: status=paid. Una persona con experiencia lo tiene en producción en 30–60 minutos.

¿Hay API REST para tecnologías que no son PHP?

Sí. Todo es JSON sencillo sobre HTTPS, así que puedes integrarlo desde cualquier lenguaje. El SDK de PHP es solo una envoltura cómoda sobre los mismos endpoints: crear factura, consultar factura y verificar firmas de webhook.

¿Vuelve el cliente a mi web tras pagar, y en el idioma correcto?

Sí. Pasa return_url al crear la factura (o ?return_url= en el enlace de pago) y la página de pago mostrará un botón «Volver a la tienda» tanto en la pantalla de éxito como durante el pago. Pasa lang (en, ru, uk, es, de), o ?lang= en el enlace, para que quien llegue desde tu tienda en español vea la página de pago en español y no en inglés. Ambos son opcionales; return_url debe ser una URL http(s) absoluta.

¿Cómo funciona la autenticación de la API?

Las peticiones y los webhooks se autentican con HMAC-SHA256 sobre la marca de tiempo y el cuerpo en bruto usando tu api_secret, con una ventana de ±120 segundos y claves de idempotencia. Las firmas se comparan en tiempo constante para evitar ataques por tiempo.

¿Cómo consigo las claves de API?

Crea una cuenta gratuita. Tu api_key pública y tu api_secret secreta aparecen en el panel, junto con el SDK descargable y la referencia completa de webhooks. Puedes rotar las claves cuando quieras desde los ajustes.

¿Qué contiene el webhook?

Un evento JSON firmado con el identificador de la factura, tu order_id, el estado (por ejemplo paid), el importe y la moneda, y un identificador de evento único para la idempotencia. Verifica siempre la firma antes de dar por pagado un pedido y guarda el identificador del evento para no procesarlo dos veces en los reintentos.

¿Hay API para pagos masivos en cripto?

Sí. POST /v1/payout envía cripto a hasta 500 carteras en una sola petición: un {address, amount, optional tag} por línea, en cualquier moneda habilitada en tu cuenta. Usa la misma autenticación HMAC-SHA256 y la misma Idempotency-Key que la API de facturas, así que reintentar nunca crea un lote duplicado. El total del lote más la comisión por línea se reserva en tu saldo de inmediato; el Operador firma y difunde, y entonces se dispara un webhook firmado payout.sent. La validación es estricta: cualquier dirección no válida o importe con demasiados decimales rechaza el lote entero (422), para que una nómina nunca pierda un destinatario en silencio.

¿Cómo automatizo nóminas o pagos de afiliados en cripto?

Arma la lista de destinatarios en tu propio sistema y llama a POST /v1/payout una vez por ejecución con una Idempotency-Key estable (por ejemplo, payroll-2026-01). Consulta GET /v1/payout/{id} o espera el webhook payout.sent para confirmar la entrega y registrar cada hash. Enviar el lote reserva los fondos pero no los mueve por sí solo: el Operador firma y difunde, de modo que nada sale con una simple llamada a la API.

Obtener tus claves de API

Un único SDK de PHP sin dependencias: crea facturas, redirige a la página de pago y verifica los webhooks firmados. Para cualquier otro lenguaje está REST.

A qué búsquedas responde esta página