> Chytapay es una plataforma de cobros por transferencia en Argentina. Esta página es parte de la doc de la Integration API: https://chytapay.com.ar/api/
> Conciliación recomendada: por CUIL. Ambientes: test (https://integration-api.test.chytapay.com.ar) y producción (https://integration-api.chytapay.com.ar).
> Página: "Webhooks" (https://chytapay.com.ar/api/webhooks/), actualizada el 2026-10-22.
> Índice completo para asistentes de IA: https://chytapay.com.ar/llms.txt

# Webhooks

Cómo recibir los eventos de pago en tu backend: suscripción, firma, reintentos y deduplicación.

&#xA;Al terminar tenés un endpoint que recibe los eventos de Chytapay, verifica la firma, descarta duplicados y responde a tiempo. Necesitás una integración en Sandbox: si no la tenés, empezá por [Primeros pasos](https://chytapay.com.ar/api/getting-started/).

1. [Configurá la URL](https://chytapay.com.ar/api/webhooks/#configurar-urls): en el Portal
2. [Elegí los eventos](https://chytapay.com.ar/api/webhooks/#suscripcion): payment.unmatched es opt-in
3. [Verificá la firma](https://chytapay.com.ar/api/webhooks/#firma): X-Chytapay-Signature
4. [Respondé y deduplicá](https://chytapay.com.ar/api/webhooks/#entrega): 2xx en 5 s

Hay tres eventos:

| Evento                             | Cuándo sale                                                       |
| ---------------------------------- | ----------------------------------------------------------------- |
| `payment_request.payment_received` | Entró un pago y se imputó a un cobro, parcial o total.            |
| `payment_request.expired`          | Venció sin saldarse un cobro con fecha y hora.                    |
| `payment.unmatched`                | Entró plata a un CVU de cobro tuyo y no se imputó a ningún cobro. |

Crear, cancelar, dejar en borrador o vencer por día (sin hora) un cobro no genera eventos.

## 1. Configurá la URL de webhook

Tiene que ser HTTPS y alcanzable desde internet (se rechazan las direcciones privadas o internas). Cargala en [**Webhooks**](https://chytapay.com.ar/portal/webhooks) del Portal, pestaña **Destino**. El token de validación es opcional, de al menos 16 caracteres: consultá [para qué sirve](https://chytapay.com.ar/api/webhooks/#validacion).

Al registrarla, Chytapay manda un POST de prueba con `{"test": true, "timestamp": "..."}` y el header `X-Chytapay-Webhook-Test: true`. Respondé 2xx y no lo proceses.

### A qué URL llega cada evento

Cada evento va a una sola URL, la de la primera regla que aplica:

1. El `webhookUrl` del cobro, si se creó con uno (recibe `validation-token` vacío). Solo para `payment_request.*`: `payment.unmatched` no lo usa.
2. La URL de la sede de la cuenta de comercio. Si la sede no tiene, no se entrega.
3. Sin sede, la URL de webhook de la integración. Si no hay, no se entrega (y con más de una, en integraciones anteriores, tampoco).

Un evento sin destino se descarta y no se reintenta, aunque corrijas la configuración. Consultá cómo asignar una URL por sede en [Sedes](https://chytapay.com.ar/api/sedes/).

## 2. Elegí qué eventos recibís

Por defecto recibís los dos `payment_request.*`. Los eventos `refund.succeeded` y `refund.failed` de las [devoluciones](https://chytapay.com.ar/api/funciones/#devolucion-eventos) también vienen apagados.

> **Atención:** **`payment.unmatched` es opt-in.** Nace apagado. Un pago sin imputar que entra mientras está apagado no se manda nunca, ni siquiera cuando lo activás.

Activalo en [**Webhooks**](https://chytapay.com.ar/portal/webhooks) del Portal, pestaña **Eventos**.

La lista es una por integración y aplica a todos los destinos: la URL de la integración, la de cada sede y el `webhookUrl` de cada cobro. Una lista vacía es válida (no recibís nada). Un evento fuera del catálogo da **422**.

## 3. Verificá la firma

> **Nota:** **La firma llega casi siempre.** Si falta o es inválida, respondé 401 para que Chytapay reintente. `validation-token` es independiente y puede estar vacío; verificá la firma en todos los destinos.

`X-Chytapay-Signature` prueba que el evento es de Chytapay y que nadie cambió el cuerpo:

1. Copiá el secreto de firma (`whsec_...`, completo, uno por integración y válido para todos los destinos) de [**Webhooks**](https://chytapay.com.ar/portal/webhooks), pestaña **Destino** del Portal y guardalo, por ejemplo en `CHYTAPAY_WEBHOOK_SECRET`.
2. Leé el cuerpo **crudo**, antes de parsear: si lo volvés a serializar, la firma no coincide.
3. Separá el header `t=<unix>,v1=<hex>` en `t` (segundos Unix) y `v1` (HMAC en hexadecimal).
4. Rechazá el evento si `t` difiere en más de 300 segundos de tu hora. Cada reintento trae su propio `t`.
5. Calculá el HMAC-SHA256 de `{t}.{cuerpo crudo}` con el secreto y comparalo con `v1` en tiempo constante.
6. Si falta el header o no coincide, respondé **401**. Chytapay lo cuenta como intento fallido y lo [reintenta](https://chytapay.com.ar/api/webhooks/#entrega).

En tu backend, verificá, deduplicá y respondé:

**Node**

```js
import { createServer } from 'node:http';
import { createHmac, timingSafeEqual } from 'node:crypto';

const SECRET = process.env.CHYTAPAY_WEBHOOK_SECRET; // el valor completo, con "whsec_"
const TOLERANCE_S = 300;
const processed = new Set(); // en producción, una tabla con índice único

export function verifySignature(rawBody, header, secret, now = Math.floor(Date.now() / 1000)) {
  if (typeof header !== 'string') return false;
  const parts = header.split(',').map((part) => part.split('='));
  const t = Number(parts.find(([key]) => key === 't')?.[1]);
  const signatures = parts.filter(([key]) => key === 'v1').map(([, value]) => value);
  if (!Number.isInteger(t) || signatures.length === 0) return false;
  if (Math.abs(now - t) > TOLERANCE_S) return false;

  const expected = createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest();
  return signatures.some((signature) => {
    const received = Buffer.from(signature, 'hex');
    return received.length === expected.length && timingSafeEqual(received, expected);
  });
}

createServer((req, res) => {
  if (req.method !== 'POST' || req.url !== '/webhooks/chytapay') {
    res.writeHead(404).end();
    return;
  }
  const chunks = [];
  req.on('data', (chunk) => chunks.push(chunk));
  req.on('end', () => {
    const rawBody = Buffer.concat(chunks); // el cuerpo crudo, sin parsear
    if (!verifySignature(rawBody, req.headers['x-chytapay-signature'], SECRET)) {
      res.writeHead(401).end();
      return;
    }
    const webhookId = req.headers['x-chytapay-webhook-id'];
    if (processed.has(webhookId)) {
      res.writeHead(200).end(); // duplicado: ya lo procesaste
      return;
    }
    processed.add(webhookId);
    const evento = JSON.parse(rawBody.toString('utf8'));
    res.writeHead(200).end(); // respondé antes de 5 s
    setImmediate(() => procesar(evento));
  });
}).listen(3000);

function procesar(evento) {
  if (evento.event === 'payment.unmatched') {
    console.log('pago sin imputar', evento.reason, evento.paymentRequest?.referenceId);
    return;
  }
  // payment_request.payment_received o payment_request.expired
  console.log(evento.referenceId, evento.stateType); // total_payment, partial_payment, overdue...
}
```

**Python**

```python
# pip install flask
import hashlib
import hmac
import os
import time

from flask import Flask, request

SECRET = os.environ["CHYTAPAY_WEBHOOK_SECRET"].encode()  # el valor completo, con "whsec_"
TOLERANCE_S = 300
processed = set()  # en producción, una tabla con índice único

app = Flask(__name__)

def verify_signature(raw_body: bytes, header: str | None, secret: bytes, now: int | None = None) -> bool:
    if not header:
        return False
    parts = [part.partition("=") for part in header.split(",")]
    t = next((value for key, _, value in parts if key == "t"), "")
    signatures = [value for key, _, value in parts if key == "v1"]
    if not t.isdigit() or not signatures:
        return False
    now = int(time.time()) if now is None else now
    if abs(now - int(t)) > TOLERANCE_S:
        return False

    expected = hmac.new(secret, t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected.encode(), signature.encode()) for signature in signatures)

@app.post("/webhooks/chytapay")
def chytapay_webhook():
    raw_body = request.get_data()  # el cuerpo crudo, antes de parsear
    if not verify_signature(raw_body, request.headers.get("X-Chytapay-Signature"), SECRET):
        return "", 401

    webhook_id = request.headers.get("X-Chytapay-Webhook-Id")
    if webhook_id in processed:
        return "", 200  # duplicado: ya lo procesaste
    processed.add(webhook_id)

    evento = request.get_json()
    if evento["event"] == "payment.unmatched":
        print("pago sin imputar", evento["reason"], evento.get("paymentRequest", {}).get("referenceId"))
    else:
        # payment_request.payment_received o payment_request.expired
        print(evento["referenceId"], evento["stateType"])  # total_payment, partial_payment, overdue...
    # Si el trabajo es pesado, encolalo: la respuesta tiene que salir antes de 5 s.
    return "", 200
```

Resultado esperado: firma correcta, 200; otra firma o sin firma, 401; el mismo `X-Chytapay-Webhook-Id` otra vez, 200 sin procesar.

### Validation-token

El header `validation-token` trae el `validationToken` que cargaste con la URL (cada sede tiene el suyo), o `""` si no cargaste ninguno o si el evento va al `webhookUrl` de un cobro. Compararlo sigue siendo válido, pero la firma cubre todos los destinos.

### IP de salida

Todos los webhooks salen de `18.231.238.36`, la misma en Sandbox y Producción.

Si tu URL está detrás de un WAF, un firewall o Cloudflare (incluido Bot Fight Mode), permití esa IP. Un desafío anti-bot no lo resuelve un servidor, así que sin la excepción cada entrega falla.

El síntoma: nosotros vemos la entrega fallar y tu aplicación no registra nada, porque el borde corta el request antes de que llegue a tu código. Revisá el log de eventos de tu WAF antes de depurar el handler.

> **Atención:** La IP no reemplaza [validar la firma](https://chytapay.com.ar/api/webhooks/#firma). Permitirla solo abre el paso; quién envía lo sigue probando la firma.

## 4. Respondé a tiempo y deduplicá

Un intento sale bien con **2xx en menos de 5 segundos**; cualquier otra cosa es un fallo. Respondé apenas verifiques la firma y procesá después.

Los 8 intentos de entrega de un evento, todos con el mismo `X-Chytapay-Webhook-Id`.

- Si falla, Chytapay reintenta a 1 minuto, 5 minutos, 15 minutos, 30 minutos, 1 hora, 3 horas y 6 horas del intento anterior, con jitter de ±20%.
- Si falla el octavo, el evento no se vuelve a enviar.

Tras los **8 intentos** (unas 11 horas) no hay endpoint para reenviarlo: consultá el cobro con `GET /payment-request/{referenceId}`. Cada intento se ve en [**Webhooks**](https://chytapay.com.ar/portal/webhooks?tab=historial), pestaña **Historial**.

### Deduplicá por X-Chytapay-Webhook-Id

Un evento puede llegar más de una vez, por ejemplo si lo procesaste pero tardaste más de 5 segundos. Guardá el `X-Chytapay-Webhook-Id` con un índice único y descartá los repetidos.

- Es igual en todos los reintentos de un evento.
- Es distinto entre eventos, incluso del mismo cobro o de la misma transferencia (`payment_received` y `unmatched`).
- Depende de la URL: si la cambiás con reintentos pendientes, llegan a la nueva con otro id.

> **Atención:** **No deduplices por `referenceId` + `stateType`.** Dos pagos parciales del mismo cobro los comparten: descartarías el segundo pago.

### Orden

El orden no está garantizado, sobre todo con reintentos. Cada intento arma el cuerpo con el estado del cobro en ese momento: tomá `stateType` como el estado actual, no el orden de llegada.

## Payload

Cada evento es un `POST` con cuerpo JSON y estos headers:

| Header                  | Qué trae                                                                                                       |
| ----------------------- | -------------------------------------------------------------------------------------------------------------- |
| `Content-Type`          | `application/json`                                                                                             |
| `X-Chytapay-Signature`  | `t=<unix>,v1=<hex>` ([verificarla](https://chytapay.com.ar/api/webhooks/#firma)).                              |
| `X-Chytapay-Webhook-Id` | El id del evento, igual en sus reintentos ([deduplicar](https://chytapay.com.ar/api/webhooks/#deduplicacion)). |
| `validation-token`      | Tu token, o vacío ([validation-token](https://chytapay.com.ar/api/webhooks/#validacion)).                      |

Todos traen `event` (ruteá primero por él, porque `payment.unmatched` tiene otra forma) y `livemode` (`true` solo en un evento real de producción).

&#xA;&#xA;

&#xA;Un pago parcial con dos vencimientos, un cobro con fecha y hora vencido sin pagos y un [pago sin imputar](https://chytapay.com.ar/api/webhooks/#pago-sin-imputar) sin atribuir:

**payment_request.payment_received**

```json
{
  "referenceId": "factura-1001",
  "event": "payment_request.payment_received",
  "livemode": false,
  "requestedAmount": 25000,
  "paidAmount": 10000,
  "thisPaymentAmount": 10000,
  "reconciledPayerCuil": 20000000001,
  "reconciledPayerName": "Pagador de Prueba",
  "reconciledPayerCbu": "0000000000000000000001",
  "remainingAmount": 15000,
  "activeDueDateIndex": 1,
  "activeDueDate": "2026-10-15T02:59:59.999Z",
  "activeDueDateAmount": 25000,
  "totalDueDates": 2,
  "stateType": "partial_payment",
  "upcomingDueDateIndex": 2,
  "upcomingDueDate": "2026-10-30T02:59:59.999Z",
  "upcomingDueDateAmount": 27500,
  "upcomingAmountToPay": 17500,
  "metadata": { "order_id": "abc-123" }
}
```

**payment_request.expired**

```json
{
  "referenceId": "reserva-2002",
  "event": "payment_request.expired",
  "livemode": false,
  "requestedAmount": 15000,
  "paidAmount": 0,
  "remainingAmount": 15000,
  "activeDueDateIndex": 1,
  "activeDueDate": "2026-10-01T18:30:00.000Z",
  "activeDueDateAmount": 15000,
  "totalDueDates": 1,
  "stateType": "overdue"
}
```

**payment.unmatched**

```json
{
  "event": "payment.unmatched",
  "livemode": false,
  "reason": "no_pending",
  "amount": 1234.56,
  "operationDate": "2026-10-01T15:04:05.000Z",
  "payerCuil": 20000000001,
  "payerName": "Pagador de Prueba",
  "payerCbuCvu": "0000000000000000000001",
  "creditCvu": "0000000000000000000002",
  "merchantUserId": "00000000-0000-4000-8000-000000000001",
  "merchantTaxId": "30712345671",
  "merchantName": "Comercio de Prueba"
}
```

### Campos de payment_request.\*

Los dos eventos `payment_request.*` tienen la misma forma y reflejan el cobro al momento del envío. Vienen siempre, salvo donde se indica:

| Campo                                                                                     | Qué es                                                                                               |
| ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `referenceId`                                                                             | El que mandaste al crear el cobro.                                                                   |
| `stateType`                                                                               | El estado del cobro (tabla de abajo).                                                                |
| `requestedAmount`                                                                         | El monto original, sin recargo.                                                                      |
| `paidAmount`                                                                              | Lo pagado hasta ahora.                                                                               |
| `thisPaymentAmount`                                                                       | El pago que disparó el evento. Solo en `payment_request.payment_received`.                           |
| `remainingAmount`                                                                         | `0` en `total_payment`; en otro estado, `activeDueDateAmount - paidAmount`, nunca negativo.          |
| `activeDueDateIndex`                                                                      | El vencimiento activo: `1` o `2`.                                                                    |
| `activeDueDate`                                                                           | Su fecha, en ISO UTC.                                                                                |
| `activeDueDateAmount`                                                                     | Su monto, con recargo si corresponde.                                                                |
| `totalDueDates`                                                                           | Cuántos vencimientos tiene.                                                                          |
| `upcomingDueDateIndex`, `upcomingDueDate`, `upcomingDueDateAmount`, `upcomingAmountToPay` | El próximo vencimiento, su monto y lo que faltaría pagar. Si hay uno y el cobro no está saldado.     |
| `reconciledPayerName`, `reconciledPayerCbu`                                               | Titular y cuenta de origen (CBU o CVU). En pagos, si se conoce.                                      |
| `reconciledPayerCuil`                                                                     | CUIL/CUIT de la cuenta que transfirió, si se conoce, en ambos modos; se omite si es el del comercio. |
| `metadata`                                                                                | Tal cual la mandaste, si la mandaste.                                                                |

Montos en pesos con dos decimales. Un vencimiento por día sale como el fin de ese día en hora argentina, en UTC. Los opcionales se omiten, no llegan en `null`.

| `stateType`                      | Qué significa                                                                                                                                                             |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `partial_payment`                | Se pagó una parte y queda saldo.                                                                                                                                          |
| `total_payment`                  | Pagado; si ya entró plata, un saldo menor a $1 también cuenta como pagado.                                                                                                |
| `overdue`                        | Venció sin pagos.                                                                                                                                                         |
| `partial_overdue`                | Venció con una parte pagada.                                                                                                                                              |
| `refunded`, `partially_refunded` | Se devolvió todo o una parte. No generan un evento `payment_request.*`: los ves en el cobro y en [`refund.*`](https://chytapay.com.ar/api/funciones/#devolucion-eventos). |
| `pending`, `draft`, `canceled`   | Estados válidos que no generan eventos.                                                                                                                                   |

No existe `PAID`: el pago total es `total_payment`. Consultá el [ciclo de vida del cobro](https://chytapay.com.ar/api/crear-cobros/#ciclo-de-vida).

## Pagos sin imputar: payment.unmatched

La plata queda acreditada en la cuenta de comercio igual: sin este evento no te enterás. Hay que [activarlo](https://chytapay.com.ar/api/webhooks/#suscripcion).

### Dos formas del mismo evento

`reason` viaja siempre. El objeto `paymentRequest` (a qué cobro correspondía) viene solo si se pudo atribuir:

| `reason`               | ¿Trae `paymentRequest`? | Qué pasó                                                                                                                                                                          |
| ---------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `expired`              | Sí                      | El cobro ya había vencido.                                                                                                                                                        |
| `already_paid`         | Sí                      | Entró plata al CVU de un cobro saldado hace menos de 24 h (en modo CUIL, del mismo pagador). No se compara el monto: no prueba que el pago sea de ese cobro.                      |
| `canceled`             | Sí                      | El cobro estaba cancelado.                                                                                                                                                        |
| `refunded`             | Sí                      | El cobro ya estaba devuelto, total o en parte, y no admite más plata.                                                                                                             |
| `partial_not_accepted` | Sí                      | Tu integración tiene la [devolución automática por pago de menos](https://chytapay.com.ar/api/funciones/#devolucion-automatica): el pago era menor al total y se devolvió entero. |
| `no_cuil_match`        | No                      | En un CVU compartido, el CUIL de quien pagó no es el de ningún pagador. Pasa cuando paga un tercero.                                                                              |
| `no_pending`           | No                      | No hay un cobro abierto ni uno cerrado atribuible en las últimas 24 h en ese CVU.                                                                                                 |
| `ambiguous`            | No                      | Más de un cobro candidato. No depende de tu integración.                                                                                                                          |



```json
{
  "event": "payment.unmatched",
  "livemode": false,
  "reason": "expired",
  "amount": 1234.56,
  "operationDate": "2026-10-01T15:04:05.000Z",
  "payerCuil": 20000000001,
  "payerName": "Pagador de Prueba",
  "payerCbuCvu": "0000000000000000000001",
  "creditCvu": "0000000000000000000002",
  "merchantUserId": "3f8a1c52-7d4e-4b19-9a60-2c5e8d1f0b47",
  "merchantTaxId": "30712345671",
  "merchantName": "Comercio de Prueba",
  "tenantKey": "sucursal-centro",
  "paymentRequest": {
    "referenceId": "reserva-2002",
    "dueDate": "2026-10-01T18:30:00.000Z"
  }
}
```

- Mirá si existe `paymentRequest` para saber si está atribuido. Si no, no viene (no llega en `null`).
- `referenceId` va adentro de `paymentRequest`, nunca en la raíz: así un handler que no mira `event` no marca pagado un cobro. Puede faltar si el cobro se creó sin `referenceId`; `dueDate` viaja siempre.
- `amount` es la plata que entró, `payerCbuCvu` la cuenta de origen (CBU o CVU) y `creditCvu` el CVU de cobro. `payerCuil`, `payerName`, `payerCbuCvu` y `creditCvu` se omiten si no se conocen.
- Para identificar al comercio que recibió la transferencia usá `merchantTaxId` (el CUIT/CUIL del titular de la cuenta, sin guiones) o `merchantUserId`. Son los mismos valores que devuelve [`GET /me`](https://chytapay.com.ar/api/conectar-comercios/#confirmar-comercio) como `accountHolderTaxId` y `merchantUserId`. `merchantName` es su nombre de fantasía (`fantasyName` en `/me`), para mostrarlo. Los tres viajan siempre.
- `tenantKey` es la clave de la [sede](https://chytapay.com.ar/api/sedes/) y solo viaja si el comercio está en una; si no, se omite (no llega en `null`). No identifica al comercio: no lo uses para eso.

> **Nota:** **Corregir el pagador puede imputar un `no_cuil_match` o un `no_pending`.** Si con `PATCH /payment-request/{referenceId}/customer` cargás el documento de quien transfirió, la plata que ya entró al CVU de ese cobro se imputa y llega `payment_request.payment_received`. Las condiciones están en [corregir el pagador](https://chytapay.com.ar/api/conciliacion/#corregir-pagador). Por eso no vuelvas a cobrar ni devuelvas de forma automática: si no se imputa, la gestión va por soporte de Chytapay. Consultá cómo [evitar el pagador tercero](https://chytapay.com.ar/api/conciliacion/#pagador-tercero).

### Dos eventos distintos con "expired"

Distinguilos por `event`: `payment_request.expired` es un cobro que venció sin que entrara plata; `payment.unmatched` con `reason: "expired"` es plata que entró cuando el cobro ya había vencido.

## 5. Probá tu endpoint en Sandbox

En Sandbox generás eventos sin mover plata; en Producción estas herramientas no existen.

### Webhook de prueba

En [**Webhooks**](https://chytapay.com.ar/portal/webhooks), pestaña **Destino**, **Probar el webhook** manda un `payment.unmatched` ficticio, firmado como uno real, para probar tu parser con cada `reason`. Si no llega, revisá que `payment.unmatched` esté activado y que haya una URL cargada. Trae un `merchantUserId` de ejemplo, y `tenantKey` solo si elegís una sede. Cada prueba trae un `X-Chytapay-Webhook-Id` nuevo y `livemode: false`.

### Simulador de pagos

Una transferencia ficticia que pasa por la imputación real y dispara el evento real. En [**Simulador**](https://chytapay.com.ar/portal/simulate), poné el CVU de un cobro tuyo, el monto y el CUIL de quien paga.

El simulador acepta el CVU de cualquier cobro tuyo, aunque esté vencido, cancelado o pagado:

| Para recibir                       | Hacé esto                                                                                                       |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `payment_request.payment_received` | Un pago sobre un cobro pendiente.                                                                               |
| `expired`                          | Un pago sobre un cobro con fecha y hora ya vencido.                                                             |
| `canceled`                         | Un pago sobre un cobro cancelado.                                                                               |
| `already_paid`                     | Un pago al CVU de un cobro saldado hace menos de 24 h (en modo CUIL, del mismo pagador). Cualquier monto sirve. |
| `no_cuil_match`                    | En un cobro por CUIL, un pago con otro CUIL de origen.                                                          |

- [Conectar comercios](https://chytapay.com.ar/api/conectar-comercios/): OAuth, `redirect_uri` y vida de los tokens.
- [Conciliación](https://chytapay.com.ar/api/conciliacion/): Por qué CUIL, el pagador tercero y `no_cuil_match`.
- [Sedes](https://chytapay.com.ar/api/sedes/): Una URL de webhook por sede.
- [Operación y errores](https://chytapay.com.ar/api/operacion/): Ambientes, límites y paso a producción.
