Documentación de integración
Tres niveles, un mismo núcleo: botón integrado, redirección, o la API directa. Elige cuánto trabajo quieres hacer — la seguridad no cambia entre niveles.
El popup solo le dice a tu interfaz qué pintar. Lo que de verdad pasó lo confirman dos cosas, ambas servidor a servidor: el webhook firmado y la consulta de estado. Un comercio que le cree al navegador es un comercio al que le van a falsificar un "pago aprobado" con la consola abierta.
- 1. La sesión nace en tu servidor, con tu clave secreta. El monto nunca viaja desde el navegador.
- 2. Al navegador solo llega un
sessionIdpúblico, de un solo uso, con vencimiento corto. - 3.
onApprovees una señal de interfaz, no un cobro — confírmalo contra tu propio backend antes de entregar el producto.
Autenticación
Cada llamada de tu servidor lleva tu clave secreta como Bearer. Nunca la pongas en el navegador.
Authorization: Bearer ezk_live_TU_CLAVE_SECRETA1 · Crear una sesión de cobro
POST /v1/sessions — devuelve el sessionId para el botón y el processUrl por si prefieres redirigir.
# El monto va entero, en la unidad menor de la moneda: 12450 = $124,50
curl -X POST https://clientes.ezipagos.com/v1/sessions \
-H "Authorization: Bearer ezk_live_TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"amount": { "amount": 12450, "currency": "USD" },
"reference": "A-2048",
"description": "Plan Aurora · 12 meses"
}'const res = await fetch('https://clientes.ezipagos.com/v1/sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.EZIPAGOS_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: { amount: 12450, currency: 'USD' },
reference: 'A-2048',
description: 'Plan Aurora · 12 meses',
}),
});
const { data } = await res.json();
// data.sessionId → para el botón
// data.processUrl → para redirigir directo$ch = curl_init('https://clientes.ezipagos.com/v1/sessions');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('EZIPAGOS_SECRET_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => ['amount' => 12450, 'currency' => 'USD'],
'reference' => 'A-2048',
'description' => 'Plan Aurora · 12 meses',
]),
]);
$body = json_decode(curl_exec($ch), true);
$sessionId = $body['data']['sessionId'];import os, requests
response = requests.post(
"https://clientes.ezipagos.com/v1/sessions",
headers={"Authorization": f"Bearer {os.environ['EZIPAGOS_SECRET_KEY']}"},
json={
"amount": {"amount": 12450, "currency": "USD"},
"reference": "A-2048",
"description": "Plan Aurora · 12 meses",
},
)
session_id = response.json()["data"]["sessionId"]2 · Embeber el botón
El popup se abre en el clic, antes de que tu backend responda — si esperas al fetch, el navegador lo bloquea. El SDK ya resuelve esto.
<script src="https://js.ezipagos.com/v1"></script>
<div id="ezipagos-button"></div>
<script>
Ezipagos.Button({
// Tu backend crea la sesión con tu clave secreta y devuelve el sessionId.
createSession: () =>
fetch('/api/pagos/crear', { method: 'POST' })
.then((r) => r.json())
.then((d) => d.sessionId),
// Señal de interfaz: el pagador terminó. Todavía no es dinero confirmado.
onApprove: ({ sessionId }) =>
fetch(`/api/pagos/${sessionId}/confirmar`, { method: 'POST' })
.then(() => { window.location = '/gracias'; }),
onPending: () => mostrarAviso('Tu pago está en revisión, te avisamos por correo.'),
onCancel: () => {},
onError: (err) => mostrarAviso(err.message),
}).render('#ezipagos-button');
</script>3 · Verificar el webhook
El navegador puede morir después de que el banco aprobó. El webhook llega igual, firmado con HMAC-SHA256 sobre timestamp.cuerpo, en el header Ezzipay-Signature con forma t=…,v1=….
import crypto from 'node:crypto';
function verifyWebhook(rawBody, header, secret, toleranceSeconds = 300) {
const [tPart, vPart] = header.split(',');
const timestamp = Number(tPart.split('=')[1]);
const signature = vPart.split('=')[1];
const expected = crypto.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`).digest('hex');
const fresco = Math.abs(Date.now() / 1000 - timestamp) <= toleranceSeconds;
return fresco && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// Monta esta ruta con el body CRUDO (express.raw), nunca ya parseado a JSON.
app.post('/webhooks/ezipagos', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.header('ezzipay-signature');
if (!verifyWebhook(req.body.toString('utf8'), header, process.env.EZIPAGOS_WEBHOOK_SECRET)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
res.sendStatus(200);
});function verify_webhook(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
[$tPart, $vPart] = explode(',', $header);
$timestamp = (int) explode('=', $tPart)[1];
$signature = explode('=', $vPart)[1];
$expected = hash_hmac('sha256', "{$timestamp}.{$rawBody}", $secret);
$fresco = abs(time() - $timestamp) <= $tolerance;
return $fresco && hash_equals($expected, $signature);
}
$rawBody = file_get_contents('php://input');
$header = $_SERVER['HTTP_EZZIPAY_SIGNATURE'] ?? '';
if (!verify_webhook($rawBody, $header, getenv('EZIPAGOS_WEBHOOK_SECRET'))) {
http_response_code(400);
exit;
}
$event = json_decode($rawBody, true);
http_response_code(200);import hmac, hashlib, time, os
def verify_webhook(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
t_part, v_part = header.split(",")
timestamp = int(t_part.split("=")[1])
signature = v_part.split("=")[1]
payload = f"{timestamp}.{raw_body.decode()}".encode()
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
fresco = abs(time.time() - timestamp) <= tolerance
return fresco and hmac.compare_digest(expected, signature)
@app.route("/webhooks/ezipagos", methods=["POST"])
def webhook():
header = request.headers.get("Ezzipay-Signature", "")
if not verify_webhook(request.get_data(), header, os.environ["EZIPAGOS_WEBHOOK_SECRET"]):
abort(400)
event = request.get_json()
return "", 200Referencia rápida
| Método | Ruta | Qué hace |
|---|---|---|
| POST | /v1/sessions | Abre una sesión de cobro |
| GET | /v1/sessions/:id | Consulta el estado — la verdad, servidor a servidor |
| GET | /v1/connectors | Bancos habilitados para tu comercio |