> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.fygaro.com/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# Botón de Pago - Hook


## Los botones de pago personalizados ofrecen la posibilidad de enviar notificaciones cuando se realiza un pago exitoso, esto se logra mediante la configuración de un hook.

### Activar *Hook* para un Botón de Pago

Activar un hook toma solo 2 pasos:

1. Definir la credencial predeterminada para hooks, que se utilizará para firmar cada solicitud y te permitirá validar la autenticidad de la información.
2. Configurar el URL al cual enviaremos las solicitudes.

Para utilizar el Hook, primero debes crear un botón de pago dinámico: [Ver Tutorial](https://help.fygaro.com/es/article/boton-de-pago-1k2y1kt/)

|| Para realizar pruebas, puede utilizar este [Proyecto de Postman](https://www.postman.com/orbital-module-cosmonaut-25453527/payment-button-sandbox/collection/3vxuy6p/fygaro-webhooks?action=share&creator=21282867).

### 1. Definir una Credencial Predeterminada para Hooks

Para definirla ingresa en **Configuración** > **Credenciales API**

![](https://storage.crisp.chat/users/helpdesk/website/a3926d4c1602c000/1c868157-502c-45d0-bac2-b0f206_fndpua.png)

En el ejemplo de arriba podemos ver la opción donde escogeremos de una lista desplegable, la credencial que usaremos por defecto para los hooks.

### 2. Configurar el URL de tu Sistema

El segundo paso es configurar la ruta a la cual se harán las solicitudes cada vez que se reciban pagos exitosos en un botón de pago personalizado. Algunas consideraciones a tener presente:

1. Debe ser una ruta pública (accesible desde Internet).
2. Debe tener un certificado de seguridad válido (ruta debe utilizar HTTPS).

Para configurar el URL, simplemente ingresa a el/los botones de pago en los que quieres activar el *Hook*, haz click en editar y posteriormente en “Opciones Avanzadas”.

Una vez en las Opciones Avanzadas encontrarás un campo llamado “URL del Hook” donde podrás colocar la ruta.

![](https://storage.crisp.chat/users/helpdesk/website/a4efc7cf80c40000/da253595-08ee-444e-a9be-019cca_1stdaju.png)



## Validación de Firma
### 1. Librerías oficiales

Se recomienda usar nuestras librerías oficiales:

| Entorno | Paquete | Docs & Install |
| ---- |
| **Python ≥ 3.8** | `fygaro-webhook` | [`pypi.org/project/fygaro-webhook`](https://pypi.org/project/fygaro-webhook/) |
| **Node ≥ 16** | `@fygaro/webhook` | [`npmjs.com/package/@fygaro/webhook`](https://www.npmjs.com/package/@fygaro/webhook) |

> **En resumen:** Instala el paquete, crea un validador con tu(s) secreto(s) compartido(s), llama a verify\_signature y solo procesa el JSON si devuelve True (Python) o true (JS).

---

### 2. Algoritmo manual (para otros lenguajes)

Si no hay una librería disponible, implementa estos pasos:

1. **Lee** el cuerpo crudo de la solicitud (en bytes).
2. **Analiza** la cabezera `Fygaro-Signature` → extrae el timestamp `t=` y cada has `v1=`.
3. **Analiza** el  `Fygaro-Key-ID` para saber cual secret utilizar.
4. **(Recomendado)** rechazar si `abs(now - t) > 300 s` (protección básica contra reproducción).
5. **Re-crea** el mensaje exactamente como lo hizo Fygaro:

```
   message = t + "." + raw_body
```

6. **Calcula** `HMAC-SHA-256(secret, message)` → hex.
7. **Constant-time compare** el resultado contra **cada** `v1`; acepta si alguno coincide.
8. En caso exitoso, **responde con HTTP 200** y continua procesando el JSON; de lo contrario retorna 4xx.

---

### 3. Ejemplos por lenguaje

#### PHP ≥ 7.4 (sin importar el framework)

```php
<?php
// 1. Map Fygaro-Key-ID → shared secret (raw string)
$secrets = [
    '1234abcd' => 'your-secret-here',
];

// 2. Read raw body & headers
$rawBody = file_get_contents('php://input') ?: '';
$sigHdr  = $_SERVER['HTTP_FYGARO_SIGNATURE'] ?? '';
$keyId   = $_SERVER['HTTP_FYGARO_KEY_ID']    ?? '';

if ($sigHdr === '' || $keyId === '') {
    http_response_code(400);
    exit('Missing Fygaro headers');
}

// 3. Parse Fygaro-Signature  (t=...,v1=...,v1=...)
$timestamp = null;
$hashes    = [];
foreach (explode(',', $sigHdr) as $part) {
    [$k, $v] = array_map('trim', explode('=', $part, 2));
    if ($k === 't')  { $timestamp = $v; }
    if ($k === 'v1') { $hashes[]  = $v; }
}
if (!$timestamp || !$hashes || !isset($secrets[$keyId])) {
    http_response_code(400);
    exit('Malformed signature header');
}

// 4. Optional replay check (±5 min window)
if (abs(time() - (int)$timestamp) > 300) {
    http_response_code(400);
    exit('Stale timestamp');
}

// 5. Compute expected HMAC
$secret   = $secrets[$keyId];
$message  = $timestamp . '.' . $rawBody;
$expected = hash_hmac('sha256', $message, $secret);

// 6. Validate: any v1 must match
$valid = false;
foreach ($hashes as $h) {
    if (hash_equals($expected, $h)) {
        $valid = true;
        break;
    }
}
if (!$valid) {
    http_response_code(400);
    exit('Invalid signature');
}

// 7. Signature verified → process payload
$data = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);

/* …your business logic… */

// MUST reply with HTTP 200 so Fygaro marks the hook successful
http_response_code(200);
echo 'OK';
```

*(Para C#, Ruby, Go, etc., adapta el manejo de cabeceras/cuerpo y cálculo HMAC según el lenguaje.)*

---

### 4. Mejores prácticas

* **Guarda los secretos de forma segura** (variables de entorno, vaults).
* **Rota los secretos** de manera suave: mantén ambos (viejo y nuevo) hasta que todo el tráfico use el nuevo.
* **Siempre** usa comparación en tiempo constante (`hash_equals`, `timingSafeEqual`, etc.).
* **Nunca** registres secretos o HMACs completos—solo razones de fallo e IDs de solicitud.
* **Responde con HTTP 200** rápidamente en caso de éxito; Fygaro lo interpreta como “entregado”.

---

## Webhook del Botón de Pago
### 1. Webhook **payload** (`POST` body)

All monetary values are **strings** with two decimals.

```jsonc
{
  "transactionId": "08d7360a-fc4b-46ad-a513-0a3d3fd3771c",
  "reference": "ORDER-98765",
  "customReference": "INV-2025-0420",
  "authCode": "A12345",
  "currency": "USD",
  "amount": "59.99",
  "createdAt": "2025-06-20T14:32:07Z",

  // ↓ The next three blocks are OPTIONAL; they are sent only when data exists.
  "card": {
    "last4": "4242",
    "expMonth": 12,
    "expYear": 2030,
    "brand": "Visa"
  },

  "client": {
    "email": "jane.doe@example.com",
    "phone": "15551234567"
  },

  "billing": {
    "country":  { "name": "United States", "code": "US" },
    "state":    { "name": "California" },
    "city":     { "name": "Los Angeles" },
    "locality": { "name": "Hollywood" },
    "address":  "123 Main St Apt 4B",
    "postal_code": "90028"
  },

  "gratuity_amount": "5.00",   // Present only when tips are captured

  "jwt": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjEyMzRhYmNkIn0…"   // LEGACY; will be removed
}
```

### Referencia de campos

| Field | Type | Description |
| ---- |
| `transactionId` | string (UUID) | ID de transacción de Fygaro inmutable. |
| `reference` | string | Referencia de pago. |
| `customReference` | string \ | null | Código de referencia ingresado por su organización. |
| `authCode` | string \ | null | Código de autorización del emisor |
| `currency` | string | ISO 4217 code (e.g., `USD`). |
| `amount` | string | Monto capturado (`"59.99"`). |
| `createdAt` | ISO-8601 string | UTC timestamp de cuando el pago fue creado. |
| `gratuity_amount` | string \ | absent | Monto de propina capturado cuando disponible |
| `card` | object \ | absent | Datos de la tarjeta de pago. |
| `client` | object \ | absent | Datos de contacto del comprador. |
| `billing` | object \ | absent | Información de facturación estructurada. |
| `jwt` | string | **Deprecated.** Present for backward compatibility only. |

> **Aviso:** comienza a migrar fuera del campo `jwt` ahora; futuras versiones del API lo omitirán.

#### `card` object

| Campo | Tipo | Descripción |
| ---- |
| `last4` | string \ | null | The four right-most digits of the card PAN. |
| `expMonth` | integer \ | null | Mes de expiración **1-12**. `null` si no disponible. |
| `expYear` | integer \ | null | 4 dígitos año expiración (e.g., `2030`). `null` si no disponible. |
| `brand` | string | Uno de: `Visa`, `MasterCard`, `American Express`, `Discover Card`, `Diners Club`, `JCB`, `unionpay`, `maestro`, o `unknown`. |
| `walletProvider ` | string \ | null | El nombre de la Billetera Digital usada, cuando aplique. |


#### `client` object

| Campo | Tipo | Descripción |
| ---- |
| `name` | string | Nombre del comprador. |
| `email` | string \ | absent | Email del comprador |
| `phone` | string \ | absent | Teléfono del comprador, si fue solicitado en el checkout. |

#### `billing` object

| Campo | Tipo | Descripción |
| ---- |
| country.name | string \ | null | Nombre completo del país. |
| `country.code` | string \ | null | ISO 3166-1 alpha-2 code (`"CR"`). |
| state.name | string \ | null | Estado / provincia / región. |
| city.name | string \ | null | Ciudad / canton. |
| locality.name | string \ | null | Vecindario / districto. |
| `address` | string \ | null | Dirección exacta. |
| `postal_code` | string \ | null | Postal / ZIP code. |

---

Necesita ayuda? → soporte@fygaro.com