n1coePayDocs

Empezar

Configura tus webhooks

Recibe las notificaciones de tu tienda y comprueba que vienen de n1co.

≈ 5 minutos · curl, Node.js, .NET, PHP o Java

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.

POST /v1/setup
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

webhooks.mjs
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

N1coWebhook.cs
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

webhooks.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)

webhooks.php
<?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

N1coWebhook.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.

POST /v1/webhooks/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.

Escribe para buscar en la documentación.