Empezar
Configura tus webhooks
Recibe las notificaciones de tu tienda y comprueba que vienen de n1co.
Paso 1Registra tu URL
Tu webhook_url se configura con POST /v1/setup al obtener tus llaves. Para cambiarla, repite el llamado con la nueva URL; la respuesta trae un webhook.secret nuevo.
La URL usa https, un host público y como máximo 2048 caracteres.
curl -s -X POST https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/setup \
-H 'Content-Type: application/json' \
-d '{
"client_id": "<client id de tu aplicación>",
"client_secret": "<client secret de tu aplicación>",
"webhook_url": "https://hooks.example.com/n1co"
}'Paso 2Verifica la firma
Cada entrega trae una firma HMAC-SHA256 del cuerpo crudo con tu webhook.secret. Esta función la verifica y la usan tus endpoints de captura de tarjeta y payment intents.
Verifica los bytes tal como llegan, antes de convertir el JSON.
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
// Firma: HMAC-SHA256 de "id.timestamp.cuerpo" con la llave en base64 de whsec_.
export function verifyWebhook(headers, body) {
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'] ?? '';
if (!id || !/^\d{1,15}$/.test(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const key = Buffer.from(process.env.N1CO_WEBHOOK_SECRET.slice('whsec_'.length), 'base64');
const expected = createHmac('sha256', key).update(`${id}.${timestamp}.`).update(body).digest();
return (headers['webhook-signature'] ?? '').split(' ').some((entry) => {
const given = Buffer.from(entry.slice(3), 'base64');
return entry.startsWith('v1,') && given.length === expected.length && timingSafeEqual(given, expected);
});
}.NET
using System.Security.Cryptography;
using System.Text;
// Firma: HMAC-SHA256 de "id.timestamp.cuerpo" con la llave en base64 de whsec_.
static class N1coWebhook
{
public static bool Verify(string? id, string? timestamp, string? signatures, byte[] body)
{
if (string.IsNullOrEmpty(id) || timestamp is not { Length: > 0 and <= 15 } || !timestamp.All(char.IsAsciiDigit))
return false;
if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(timestamp)) > 300) return false;
var secret = Environment.GetEnvironmentVariable("N1CO_WEBHOOK_SECRET")!;
var key = Convert.FromBase64String(secret["whsec_".Length..]);
byte[] signed = [.. Encoding.UTF8.GetBytes($"{id}.{timestamp}."), .. body];
var expected = HMACSHA256.HashData(key, signed);
var given = new byte[32];
foreach (var entry in (signatures ?? "").Split(' '))
{
if (entry.StartsWith("v1,") && Convert.TryFromBase64String(entry[3..], given, out var n) && n == 32
&& CryptographicOperations.FixedTimeEquals(given, expected))
return true;
}
return false;
}
}PHP
<?php
// Firma: HMAC-SHA256 de "id.timestamp.cuerpo" con la llave en base64 de whsec_.
function verify_webhook(string $body): bool
{
$id = $_SERVER['HTTP_WEBHOOK_ID'] ?? '';
$timestamp = $_SERVER['HTTP_WEBHOOK_TIMESTAMP'] ?? '';
if ($id === '' || !preg_match('/^\d{1,15}$/', $timestamp) || abs(time() - (int) $timestamp) > 300) {
return false;
}
$key = base64_decode(substr(getenv('N1CO_WEBHOOK_SECRET'), strlen('whsec_')), true);
$expected = 'v1,' . base64_encode(hash_hmac('sha256', "$id.$timestamp.$body", $key, true));
foreach (explode(' ', $_SERVER['HTTP_WEBHOOK_SIGNATURE'] ?? '') as $entry) {
if (hash_equals($expected, $entry)) {
return true;
}
}
return false;
}PHP (SDK)
<?php
use N1co\EPay\Exception\WebhookSignatureException;
use N1co\EPay\N1co;
$n1co = new N1co((string) getenv('N1CO_SECRET_KEY'), [
'webhook_secret' => (string) getenv('N1CO_WEBHOOK_SECRET'),
'base_url' => 'https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com',
]);
try {
$event = $n1co->webhooks->constructEventFromRequest(); // lee php://input y los encabezados
} catch (WebhookSignatureException) {
http_response_code(400);
exit;
}Java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Instant;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
// Firma: HMAC-SHA256 de "id.timestamp.cuerpo" con la llave en base64 de whsec_.
final class N1coWebhook {
private N1coWebhook() {}
static boolean verify(String id, String timestamp, String signatures, byte[] body) throws Exception {
if (id == null || timestamp == null || signatures == null || !timestamp.matches("\\d{1,15}")) return false;
if (Math.abs(Instant.now().getEpochSecond() - Long.parseLong(timestamp)) > 300) return false;
var key = Base64.getDecoder().decode(System.getenv("N1CO_WEBHOOK_SECRET").substring("whsec_".length()));
var mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
mac.update((id + "." + timestamp + ".").getBytes(StandardCharsets.UTF_8));
var expected = mac.doFinal(body);
for (var entry : signatures.split(" ")) {
try {
if (entry.startsWith("v1,") && MessageDigest.isEqual(Base64.getDecoder().decode(entry.substring(3)), expected)) {
return true;
}
} catch (IllegalArgumentException notBase64) {
// Una entrada que no es base64 no coincide.
}
}
return false;
}
}Paso 3Prueba tu endpoint
POST /v1/webhooks/test envía una entrega de prueba firmada a tu webhook_url y responde 202; tu endpoint recibe un webhook.test.
curl -s -X POST https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/webhooks/test \
-H "Authorization: Bearer $N1CO_SECRET_KEY"Tus webhooks están listos.
Úsalos para confirmar capturas de tarjeta y payment intents.