> ## Documentation Index
> Fetch the complete documentation index at: https://docs.velafi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Registra endpoints de webhook para recibir callbacks HTTP inmediatos de VelaFi sobre cambios de estado de órdenes, eventos de fondeo y actualizaciones de pagos globales.

Los webhooks permiten que VelaFi envíe notificaciones en tiempo real a tu servidor en el momento en que ocurre algo importante: una orden cambia de estado, llegan fondos a tu cuenta o un pago global avanza en su ciclo de vida. En lugar de consultar la API una y otra vez, registras un endpoint HTTPS una sola vez y VelaFi le envía un HTTP POST con un payload JSON cada vez que se dispara un evento al que estás suscrito. Esta guía explica cómo registrar endpoints, verificar la firma de la solicitud y confirmar la entrega.

> [!WARNING]
>
> Los webhooks **no se disparan en el entorno sandbox** (`https://api-test.velafi.com`). Prueba la lógica de tu handler de webhooks simulando payloads localmente y después verifica el flujo completo en producción con transacciones de bajo valor.

## Tipos de evento disponibles

Registra un webhook por separado para cada tipo de evento que quieras recibir. La creación de un webhook se rechaza si ya existe un webhook activo para ese tipo de evento, y un webhook recibe su evento para todos los comercios de tu cuenta. VelaFi admite los siguientes tipos de evento:

> [card] ORDER\_WEBHOOK
>
> Se dispara con los cambios de estado de las órdenes de fiat a cripto (on-ramp) y de cripto a fiat (off-ramp) creadas a través de la API.

> [card] GLOBAL\_PAYMENT\_WEBHOOK
>
> Se dispara con los cambios de estado de las órdenes de pagos globales de fiat a fiat creadas a través de la API.

> [card] FUNDING\_WEBHOOK
>
> Se dispara cuando un registro de fondeo (un depósito fiat, un retiro fiat, una compra de cripto o una transferencia interna en la cuenta fiat (Pending Fund) del comercio) se completa, se cancela o se reembolsa. Los retiros de la billetera cripto se cubren con `CRYPTO_WITHDRAW_WEBHOOK`.

> [card] ON\_RAMP\_WITHDRAW\_WEBHOOK
>
> Se dispara cuando VelaFi ya retiró la cripto de una orden on-ramp completada por API hacia la dirección de billetera de destino de la orden.

> [card] STABLE\_COIN\_PAYMENT\_WEBHOOK
>
> Se dispara cuando se confirma una orden de pago con stablecoin.

> [card] [CRYPTO\_WITHDRAW\_WEBHOOK](https://docs.velafi.com/es/api-reference/webhooks/crypto-withdraw-message-push.md)
>
> Se dispara cuando un retiro de cripto desde la billetera de un comercio es exitoso, falla o se cancela.

## Registrar un endpoint de webhook

### Envía la solicitud de registro

Llama a `POST /v2/webhook` con el tipo de evento y la URL HTTPS que quieres que VelaFi llame.

**Endpoint:** `POST /v2/webhook`

#### Parámetros de la solicitud

**eventType** `string` *required*

El tipo de evento al que te suscribes: uno de los tipos de evento listados arriba (`ORDER_WEBHOOK`, `GLOBAL_PAYMENT_WEBHOOK`, `FUNDING_WEBHOOK`, `ON_RAMP_WITHDRAW_WEBHOOK`, `STABLE_COIN_PAYMENT_WEBHOOK` o `CRYPTO_WITHDRAW_WEBHOOK`).

**url** `string` *required*

La URL completa de tu endpoint de callback (por ejemplo, `https://yourapp.com/webhooks/velafi`). Debe comenzar con `http`. Usa una URL `https://`. El endpoint debe ser accesible públicamente y capaz de procesar solicitudes HTTP POST.

#### Ejemplo de solicitud

```bash theme={null}
curl -X POST https://api.velafi.com/v2/webhook \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "eventType": "ORDER_WEBHOOK",
    "url": "https://yourapp.com/webhooks/velafi"
  }'
```

### Guarda la publicKey de la respuesta

La respuesta devuelve un `webhookId` y, lo más importante, una `publicKey` que usas para verificar la firma de cada solicitud de webhook entrante. Guarda esta clave de forma segura. Si la pierdes, `GET /v2/webhooks` la vuelve a devolver.

#### Ejemplo de respuesta

```json theme={null}
{
  "code": 200,
  "msg": "SUCCESS",
  "data": {
    "webhookId": "ebab8a9663704e7cbbebe672c7c67912",
    "eventType": "ORDER_WEBHOOK",
    "url": "https://yourapp.com/webhooks/velafi",
    "status": 10,
    "publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp/6GRTIrl+kQb9u27cGz..."
  }
}
```

#### Campos de la respuesta

**webhookId** `string`

Identificador único de este registro de webhook.

**eventType** `string`

El tipo de evento al que te suscribiste, como confirmación de tu solicitud.

**url** `string`

La URL de callback a la que VelaFi hará POST cuando se disparen los eventos.

**status** `number`

Estado de la configuración del webhook: `10` = activo, `20` = deshabilitado, `30` = eliminado.

**publicKey** `string`

Clave pública RSA (codificada en Base64, formato X.509) que se usa para verificar el header `signature` de las solicitudes de webhook entrantes. Guárdala de forma segura.

## Verificar la firma del webhook

Cada solicitud que VelaFi envía a tu endpoint incluye un header `signature`. Su valor es una firma RSA-SHA256 del cuerpo crudo de la solicitud, codificada como cadena hexadecimal. Verifica siempre esta firma antes de procesar el payload para asegurarte de que la solicitud realmente proviene de VelaFi.

Los pasos de verificación son:

1. Extrae el valor de `signature` del header de la solicitud.
2. Decodifica de hexadecimal el valor de `signature`.
3. Verifícalo como una firma RSA-SHA256 (`SHA256withRSA`, PKCS#1 v1.5) sobre el cuerpo crudo exacto de la solicitud, usando la `publicKey` de la respuesta del registro del webhook.
4. Si la verificación es exitosa, procesa el payload y devuelve `{"callbackStatus": "SUCCESS"}`.

> [tab] Java
>
> ```java theme={null}
> import java.security.*;
> import java.security.spec.X509EncodedKeySpec;
> import java.util.Base64;
> import javax.servlet.http.HttpServletRequest;
>
> public class CallbackController {
>
>     // Reemplaza con la publicKey devuelta al registrar el webhook
>     private String publicKey = "YOUR_PUBLIC_KEY";
>
>     public String callbackNotice(HttpServletRequest request, @RequestBody String param) {
>         // 1. Extrae el header signature
>         String signature = request.getHeader("signature");
>
>         // 2. Verifica con RSA-SHA256 (la firma está codificada en hexadecimal)
>         boolean verified = verifyRSASHA256(param, signature, publicKey);
>         System.out.println(String.format("verified:%s", verified));
>
>         return String.format(
>             "{\"callbackStatus\":\"%s\"}",
>             verified ? "SUCCESS" : "FAIL"
>         );
>     }
>
>     public static boolean verifyRSASHA256(String data, String signature, String publicKeyStr) {
>         try {
>             // Decodifica la clave pública codificada en Base64
>             byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyStr);
>             X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
>
>             // Reconstruye la clave pública RSA
>             KeyFactory keyFactory = KeyFactory.getInstance("RSA");
>             PublicKey publicKey = keyFactory.generatePublic(keySpec);
>
>             // Inicializa el verificador de Signature
>             Signature sig = Signature.getInstance("SHA256withRSA");
>             sig.initVerify(publicKey);
>
>             // Pasa los bytes del payload
>             sig.update(data.getBytes("UTF-8"));
>
>             // Decodifica la firma hexadecimal y verifica
>             byte[] signatureBytes = decodeHex(signature);
>             return sig.verify(signatureBytes);
>         } catch (Exception e) {
>             e.printStackTrace();
>             return false;
>         }
>     }
>
>     public static byte[] decodeHex(String value) {
>         int len = value.length();
>         byte[] data = new byte[len / 2];
>         for (int i = 0; i < len; i += 2) {
>             data[i / 2] = (byte) (
>                 (Character.digit(value.charAt(i), 16) << 4)
>                 + Character.digit(value.charAt(i + 1), 16)
>             );
>         }
>         return data;
>     }
> }
> ```

> [tab] Python
>
> ```python theme={null}
> import json
> from flask import request
>
> # Reemplaza con la publicKey devuelta al registrar el webhook
> PUBLIC_KEY = "YOUR_PUBLIC_KEY"
>
> def callback_notice():
>     # 1. Lee el cuerpo crudo y el header signature
>     param = request.get_data(as_text=True)
>     signature = request.headers.get("signature")
>
>     # 2. Verifica RSA-SHA256 (la firma está codificada en hexadecimal)
>     verified = sha256_rsa_verify(param, signature, PUBLIC_KEY)
>     print(f"verified: {verified}")
>
>     # 3. Confirma la recepción
>     return json.dumps({
>         "callbackStatus": "SUCCESS" if verified else "FAIL"
>     })
>
> def sha256_rsa_verify(data: str, signature: str, public_key_b64: str) -> bool:
>     """
>     Verifica una firma RSA-SHA256.
>
>     :param data:           La cadena del cuerpo crudo de la solicitud que se firmó.
>     :param signature:      Firma RSA-SHA256 codificada en hexadecimal tomada del header.
>     :param public_key_b64: Clave pública RSA codificada en Base64 (X.509).
>     :return:               True si la firma es válida, False en caso contrario.
>     """
>     from cryptography.hazmat.primitives import hashes, serialization
>     from cryptography.hazmat.primitives.asymmetric import padding
>     import base64
>
>     try:
>         pub_key_bytes = base64.b64decode(public_key_b64)
>         public_key = serialization.load_der_public_key(pub_key_bytes)
>         sig_bytes = bytes.fromhex(signature)
>         public_key.verify(
>             sig_bytes,
>             data.encode("utf-8"),
>             padding.PKCS1v15(),
>             hashes.SHA256()
>         )
>         return True
>     except Exception:
>         return False
> ```

> [tab] JavaScript
>
> ```javascript theme={null}
> const crypto = require("crypto");
>
> // Reemplaza con la publicKey devuelta al registrar el webhook
> const PUBLIC_KEY = "YOUR_PUBLIC_KEY";
>
> async function callbackNotice(req, res) {
>     // 1. Lee el cuerpo crudo y el header signature
>     const param = req.body; // cadena cruda: asegúrate de que body-parser esté configurado como 'text'
>     const signature = req.headers["signature"];
>
>     // 2. Verifica RSA-SHA256 (la firma está codificada en hexadecimal)
>     const verified = verifyRSASHA256(param, signature, PUBLIC_KEY);
>     console.log(`verified: ${verified}`);
>
>     // 3. Confirma la recepción
>     res.json({ callbackStatus: verified ? "SUCCESS" : "FAIL" });
> }
>
> function verifyRSASHA256(data, hexSignature, publicKeyB64) {
>     try {
>         // Reconstruye la clave pública en formato PEM a partir de Base64
>         const pem = `-----BEGIN PUBLIC KEY-----\n${publicKeyB64}\n-----END PUBLIC KEY-----`;
>         const verify = crypto.createVerify("RSA-SHA256");
>         verify.update(data, "utf8");
>         // Convierte la firma hexadecimal a Buffer
>         const sigBuffer = Buffer.from(hexSignature, "hex");
>         return verify.verify(pem, sigBuffer);
>     } catch (err) {
>         console.error(err);
>         return false;
>     }
> }
> ```

## Esquemas de payload de webhook

Cada tipo de evento entrega un cuerpo JSON distinto a tu endpoint.

> [accordion] ORDER\_WEBHOOK: órdenes on-ramp y off-ramp
>
> VelaFi envía este payload con los cambios de estado de las órdenes de fiat a cripto y de cripto a fiat creadas a través de la API. Este es un ejemplo abreviado; consulta [Evento de orden](https://docs.velafi.com/es/api-reference/webhooks/fiat-crypto-order-message-push.md) para ver todos los campos.
>
> ```json theme={null}
> {
>   "webhookId": "ebab8a9663704e7cbbebe672c7c67912",
>   "eventType": "ORDER_WEBHOOK",
>   "orderId": "473130522693193728",
>   "clientId": "W658784738723",
>   "userId": "455472",
>   "merchantId": "4",
>   "orderType": "SELL",
>   "fiat": "MXN",
>   "fiatAmount": "99",
>   "fiatFee": "0",
>   "crypto": "USDT",
>   "cryptoAmount": "5",
>   "orderPrice": "19.8158",
>   "orderStatus": "60",
>   "createTime": "1735279907000",
>   "updateTime": "1735280582000"
> }
> ```
>
> `orderType` es `BUY` para las órdenes on-ramp y `SELL` para las órdenes off-ramp. Solo se envían algunos estados, y las órdenes rechazadas o vencidas se envían como `70`. Consulta [Evento de orden](https://docs.velafi.com/es/api-reference/webhooks/fiat-crypto-order-message-push.md) para ver los estados que se envían.

> [accordion] GLOBAL\_PAYMENT\_WEBHOOK: órdenes de fiat a fiat
>
> VelaFi envía este payload con los cambios de estado de las órdenes de pagos globales de fiat a fiat creadas a través de la API.
>
> ```json theme={null}
> {
>   "webhookId": "ebab8a9663704e7cbbebe672c7c67912",
>   "eventType": "GLOBAL_PAYMENT_WEBHOOK",
>   "orderId": "473130522693193728",
>   "clientId": "W658784738723",
>   "userId": "455472",
>   "onRampCountry": "Mexico",
>   "onRampMerchantId": "3",
>   "onRampFiat": "MXN",
>   "onRampFiatAmount": "100",
>   "onRampFiatFee": "0",
>   "offRampCountry": "Hong Kong",
>   "offRampMerchantId": "4",
>   "offRampFiat": "USD",
>   "offRampFiatAmount": "18",
>   "offRampFiatFee": "2",
>   "orderPrice": "20",
>   "orderStatus": "41",
>   "createTime": "1735279907000",
>   "updateTime": "1735280582000"
> }
> ```
>
> `orderStatus` usa los mismos códigos que el endpoint de detalle de orden de [Pagos globales](https://docs.velafi.com/es/docs/guides/global-payments.md), pero solo se envían los estados listados en [Evento de pago global](https://docs.velafi.com/es/api-reference/webhooks/fiat-fiat-order-message-push.md).

> [accordion] FUNDING\_WEBHOOK: eventos de fondeo del comercio
>
> VelaFi envía este payload cuando un depósito fiat, un retiro fiat, una compra de cripto o una transferencia interna en la cuenta fiat (Pending Fund) de tu comercio se completa, se cancela o se reembolsa. Los retiros de la billetera cripto se cubren con `CRYPTO_WITHDRAW_WEBHOOK`.
>
> ```json theme={null}
> {
>   "webhookId": "ebab8a9663704e7cbbebe672c7c67912",
>   "eventType": "FUNDING_WEBHOOK",
>   "txId": "1123123123",
>   "clientId": "client123456",
>   "userId": "455472",
>   "merchantId": "3",
>   "merchantName": "Example Merchant",
>   "fiat": "ARS",
>   "totalAmount": "22",
>   "amount": "20",
>   "fee": "2",
>   "refundedAmount": "0",
>   "paymentId": "63",
>   "userPaymentId": "91",
>   "type": "DEPOSIT",
>   "status": "2",
>   "completedTime": "1737452344000",
>   "createTime": "1737452292000",
>   "updateTime": "1737452344000",
>   "cardNo": "0000000001",
>   "taxId": "20000000001",
>   "payerName": "Jane Doe",
>   "payerAccount": "0000000000000000000005",
>   "depositAlias": "alias",
>   "bankWebhookInfo": "{}",
>   "failCode": "",
>   "failReason": ""
> }
> ```
>
> El campo `type` es uno de `DEPOSIT`, `WITHDRAW`, `BUY_CRYPTO`, `TRANSFER_IN` o `TRANSFER_OUT`. El campo `status` es `2` (completado), `3` (cancelado) o `4` (reembolsado). Los registros pendientes no se envían.

## Confirmar la recepción

Tu endpoint debe devolver una respuesta HTTP `2xx` (por ejemplo `200`) con el siguiente cuerpo JSON para confirmar que la notificación se recibió y se procesó correctamente. Si VelaFi no recibe una confirmación `SUCCESS`, por ejemplo porque tu endpoint no respondió en 6 segundos, reintenta la entrega, con un total de tres intentos. Consulta [Entrega y reintentos](https://docs.velafi.com/es/api-reference/webhooks/webhook-setup.md#delivery-and-retries) para ver el calendario, el manejo de duplicados y el orden.

```json theme={null}
{
  "callbackStatus": "SUCCESS"
}
```

> [!NOTE]
>
> Devuelve `{"callbackStatus": "SUCCESS"}` en cuanto hayas verificado la firma y guardado el evento. Realiza cualquier procesamiento posterior de forma asíncrona para evitar timeouts que puedan provocar reintentos innecesarios.


## Related topics

- [Listar webhooks](https://docs.velafi.com/es/api-reference/webhooks/get-webhook-endpoints.md)
- [Configuración de webhooks](https://docs.velafi.com/es/api-reference/webhooks/webhook-setup.md)
- [Eliminar un webhook](https://docs.velafi.com/es/api-reference/webhooks/delete-a-webhook.md)
- [Crear un webhook](https://docs.velafi.com/es/api-reference/webhooks/create-webhook-endpoints.md)
- [Actualizar un webhook](https://docs.velafi.com/es/api-reference/webhooks/update-a-webhook.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.