# Webhooks

> Reciba en su servidor cada factura timbrada, rechazada o cancelada, con eventos firmados que se reintentan durante un día.

Un webhook es una dirección de su servidor a la que Fiscalisto manda un evento cada vez que cambia un comprobante de su cuenta. Con ellos su sistema se entera de lo que no pasa en la respuesta de una solicitud: una cancelación que el cliente acepta días después, una factura que el PAC tardó en timbrar o un lote que terminó.

## Tipos de evento

Son todos los que hay. Cada uno trae en `data.object` el objeto completo, igual que su `GET`, con el estado que tenía cuando pasó. Abra uno para ver el evento entero tal como llega, campo por campo.

| Evento | Cuándo llega | `data.object` |
| --- | --- | --- |
| [`invoice.stamped`](https://fiscalisto.com/docs/api/referencia/eventos/invoice-stamped) | Se timbró un CFDI: factura, complemento de pago, nota de crédito, anticipo, su nota de aplicación, sustitución o recibo de nómina. Llega también cuando uno que quedó en `processing` (el PAC no contestó) se timbra después. | Factura (`inv_…`), `status: "valid"` |
| [`invoice.failed`](https://fiscalisto.com/docs/api/referencia/eventos/invoice-failed) | El SAT o el PAC rechazaron un CFDI. Llega aunque la solicitud ya le haya contestado `422`, y cuando uno en `processing` termina rechazado. | Factura, `status: "rejected"`, con `error` |
| [`invoice.cancellation_pending`](https://fiscalisto.com/docs/api/referencia/eventos/invoice-cancellation-pending) | Pidió la cancelación y el cliente debe aceptarla en su buzón tributario (vea [cuándo](https://fiscalisto.com/docs/api/cancelacion#cuándo-debe-aceptarlo-el-cliente)). Sigue vigente mientras tanto. | Factura, `status: "pending"`, `cancellation.status: "pending"` |
| [`invoice.canceled`](https://fiscalisto.com/docs/api/referencia/eventos/invoice-canceled) | Se canceló: de inmediato, o porque el cliente aceptó o no contestó en 3 días hábiles. | Factura, `status: "canceled"`, `cancellation.status: "accepted"` |
| [`invoice.cancellation_rejected`](https://fiscalisto.com/docs/api/referencia/eventos/invoice-cancellation-rejected) | El cliente rechazó la cancelación, o el SAT nunca recibió la solicitud. Sigue vigente; puede pedirla otra vez. | Factura, `status: "valid"`, `cancellation.status: "rejected"` o `"lapsed"` |
| [`invoice.partially_paid`](https://fiscalisto.com/docs/api/referencia/eventos/invoice-partially-paid) | Llegó un pago por una parte de una factura PPD: un complemento de pago o una nota de crédito. Ver [Cuentas por cobrar](https://fiscalisto.com/docs/api/cuentas-por-cobrar). | Factura, con lo que falta en `amount_due` |
| [`invoice.paid`](https://fiscalisto.com/docs/api/referencia/eventos/invoice-paid) | Una factura de ingreso ya no debe nada: su último complemento, una nota de crédito, un depósito conciliado o `mark_paid`. | Factura, con `payment_status: paid` |
| [`retention.stamped`](https://fiscalisto.com/docs/api/referencia/eventos/retention-stamped) | Se timbró una constancia de retenciones. | Constancia (`ret_…`), `status: "valid"` |
| [`retention.failed`](https://fiscalisto.com/docs/api/referencia/eventos/retention-failed) | El SAT o el PAC rechazaron una constancia. | Constancia, `status: "rejected"` |
| [`retention.canceled`](https://fiscalisto.com/docs/api/referencia/eventos/retention-canceled) | Se canceló una constancia (no piden aceptación). | Constancia, `status: "canceled"` |
| [`invoice_batch.completed`](https://fiscalisto.com/docs/api/referencia/eventos/invoice-batch-completed) | Ya no queda ninguna factura del lote por timbrar: cada una se timbró o falló. Cada factura del lote manda además su `invoice.stamped` o `invoice.failed`. | Lote (`bat_…`), `status: "completed"` |
| [`issuer.ready`](https://fiscalisto.com/docs/api/referencia/eventos/issuer-ready) | Un emisor de producción ya puede timbrar: tiene su CSD y firmó el contrato con el PAC. En pruebas, al terminar una [liga de alta](https://fiscalisto.com/docs/api/emisores#que-su-cliente-suba-sus-archivos). | Emisor (`iss_…`), `status: "active"` |
| [`customer_data_request.completed`](https://fiscalisto.com/docs/api/referencia/eventos/customer-data-request-completed) | Un cliente mandó sus datos fiscales con una [liga](https://fiscalisto.com/docs/api/datos-del-cliente#pedirle-sus-datos-al-cliente). | Solicitud (`cdr_…`), `status: "completed"` |
| [`sat_download.completed`](https://fiscalisto.com/docs/api/referencia/eventos/sat-download-completed) | Terminó una [descarga del SAT](https://fiscalisto.com/docs/api/descargas-del-sat): sus facturas ya están en `GET /v1/sat_invoices`. | Descarga (`sdl_…`), `status: "completed"` |
| [`sat_download.failed`](https://fiscalisto.com/docs/api/referencia/eventos/sat-download-failed) | El SAT no pudo terminar una descarga; el motivo viene en `error`. | Descarga (`sdl_…`), `status: "failed"` |
| [`sat_declaration.created`](https://fiscalisto.com/docs/api/referencia/eventos/sat-declaration-created) | Una [consulta de declaraciones](https://fiscalisto.com/docs/api/declaraciones-del-sat) encontró una nueva. Sus archivos y cifras pueden llegar después. | Declaración (`dcl_…`) |
| [`sat_declaration.updated`](https://fiscalisto.com/docs/api/referencia/eventos/sat-declaration-updated) | Llegó el PDF, el acuse, los estados financieros o las cifras de una declaración. | Declaración (`dcl_…`), como queda |
| [`ticket.invoiced`](https://fiscalisto.com/docs/api/referencia/eventos/ticket-invoiced) | Su comprador facturó un ticket en el portal de [autofactura](https://fiscalisto.com/docs/api/autofactura). Llega también el `invoice.stamped` de su factura. | Ticket (`tkt_…`), `status: "invoiced"`, con la factura en `invoice` |
| [`recurring_invoice.failed`](https://fiscalisto.com/docs/api/referencia/eventos/recurring-invoice-failed) | Una [factura recurrente](https://fiscalisto.com/docs/api/facturas-recurrentes) no pudo timbrar su periodo (sin timbres, el cliente borrado, el SAT la rechazó). Llega una vez; se intenta de nuevo cada día. | Factura recurrente (`rin_…`), con el motivo en `last_error` |
| [`quote.viewed`](https://fiscalisto.com/docs/api/referencia/eventos/quote-viewed) | El cliente abrió por primera vez la liga de una [cotización](https://fiscalisto.com/docs/api/cotizaciones). | Cotización (`quo_…`) |
| [`quote.accepted`](https://fiscalisto.com/docs/api/referencia/eventos/quote-accepted) | Se aceptó una cotización: el cliente en su liga o usted con `POST /v1/quotes/{id}/accept`. | Cotización, con `decided_by` |
| [`quote.changes_requested`](https://fiscalisto.com/docs/api/referencia/eventos/quote-changes-requested) | El cliente pidió cambios en la liga. A lo más una vez cada 10 minutos. | Cotización, con lo que escribió en `customer_comment` |
| [`quote.rejected`](https://fiscalisto.com/docs/api/referencia/eventos/quote-rejected) | El cliente rechazó la cotización en su liga. | Cotización, con su motivo en `customer_comment` |
| [`ticket.included_in_global`](https://fiscalisto.com/docs/api/referencia/eventos/ticket-included-in-global) | Un ticket que nadie facturó entró en la factura global del periodo. | Ticket (`tkt_…`), `status: "global"`, con la factura global en `global_invoice` |
| [`balance.low`](https://fiscalisto.com/docs/api/referencia/eventos/balance-low) | A la cuenta le quedan pocos timbres de producción: al bajar a `low_balance_at` y otra vez al acabarse, una vez cada uno hasta que vuelva a tener más. Ver [Qué cuesta un timbre](https://fiscalisto.com/docs/api/pruebas-y-produccion#qué-cuesta-un-timbre). | Saldo (`object: "balance"`), como `GET /v1/balance`; siempre `livemode: true` |
| [`test_clock.ready`](https://fiscalisto.com/docs/api/referencia/eventos/test-clock-ready) | Terminó de adelantarse un [reloj de prueba](https://fiscalisto.com/docs/api/pruebas-y-produccion#adelantar-el-tiempo-con-un-reloj-de-prueba): ya se hizo lo de cada día. Solo en pruebas. | Reloj de prueba (`object: "test_clock"`) |

Lo que no manda evento: crear o cambiar clientes, productos, emisores y webhooks (eso lo hace su sistema y ya tiene la respuesta), y los envíos por correo.

```json title="invoice.stamped"
{
  "id": "evt_0PKhIdhYuAGwzmMQYfkc",
  "object": "event",
  "type": "invoice.stamped",
  "api_version": "2026-10-24",
  "livemode": false,
  "created_at": "2026-10-24T10:15:42-06:00",
  "data": {
    "object": { "id": "inv_OufpKqnrvx9p28SINJVjjWKYRhojBTJb8P", "object": "invoice", "status": "valid", "...": "..." }
  }
}
```

| Campo | Qué es |
| --- | --- |
| `id` | El id del evento. Es el mismo en el encabezado `webhook-id` y en cada reintento: úselo para no procesarlo dos veces. |
| `type` | Uno de los eventos de la tabla. |
| `api_version` | La versión de la API con la que se armó el objeto. |
| `livemode` | `false` si viene de pruebas (`fk_test_`), `true` de producción. |
| `created_at` | Cuándo pasó, hora de la Ciudad de México. |
| `data.object` | El objeto completo. |

La página de cada evento (en la tabla de arriba) tiene un ejemplo completo de lo que manda, y [`GET /v1/events`](https://fiscalisto.com/docs/api/referencia/eventos/list-events) devuelve los de los últimos 30 días.

## Agregar un webhook

Cada `url` va en un solo endpoint por modo, para que ningún evento llegue dos veces: crear otro con la misma contesta `400` `endpoint_exists` con el id del que ya la tiene, para cambiarlo con `POST /v1/webhook_endpoints/{id}`.

```bash tab="cURL"
curl https://api.fiscalisto.com/v1/webhook_endpoints \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://mi-plataforma.mx/webhooks/fiscalisto",
    "events": ["invoice.stamped", "invoice.failed", "invoice.canceled"]
  }'
```

```ts tab="TypeScript"
import { Fiscalisto } from "fiscalisto";

// La llave viene de FISCALISTO_API_KEY.
const fiscalisto = new Fiscalisto();

const webhookEndpoint = await fiscalisto.webhookEndpoints.create({
  url: "https://mi-plataforma.mx/webhooks/fiscalisto",
  events: [
    "invoice.stamped",
    "invoice.failed",
    "invoice.canceled",
  ],
});
```

```python tab="Python"
from fiscalisto import Fiscalisto

# La llave viene de FISCALISTO_API_KEY.
fiscalisto = Fiscalisto()

webhook_endpoint = fiscalisto.webhook_endpoints.create({
    "url": "https://mi-plataforma.mx/webhooks/fiscalisto",
    "events": [
        "invoice.stamped",
        "invoice.failed",
        "invoice.canceled",
    ],
})
```

```php tab="PHP"
<?php

use Fiscalisto\Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
$fiscalisto = new Fiscalisto();

$webhookEndpoint = $fiscalisto->webhookEndpoints->create([
    'url' => 'https://mi-plataforma.mx/webhooks/fiscalisto',
    'events' => [
        'invoice.stamped',
        'invoice.failed',
        'invoice.canceled',
    ],
]);
```

```go tab="Go"
package main

import (
	"context"
	"fmt"
	"log"

	fiscalisto "fiscalisto.com/go"
)

func main() {
	// La llave viene de FISCALISTO_API_KEY.
	client, err := fiscalisto.NewClient("")
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()

	webhookEndpoint, err := client.WebhookEndpoints.Create(ctx, &fiscalisto.CreateWebhookEndpointParams{
		URL: "https://mi-plataforma.mx/webhooks/fiscalisto",
		Events: []string{
			"invoice.stamped",
			"invoice.failed",
			"invoice.canceled",
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(webhookEndpoint.ID)
}
```

```java tab="Java"
import com.fiscalisto.FiscalistoClient;
import com.fiscalisto.model.CreateWebhookEndpointParams;
import java.util.List;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

var webhookEndpoint = fiscalisto.webhookEndpoints().create(new CreateWebhookEndpointParams()
        .setUrl("https://mi-plataforma.mx/webhooks/fiscalisto")
        .setEvents(List.of(
                "invoice.stamped",
                "invoice.failed",
                "invoice.canceled"
        )));
System.out.println(webhookEndpoint.getId());
```

```csharp tab="C#"
using Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

var webhookEndpoint = await fiscalisto.WebhookEndpoints.CreateAsync(new CreateWebhookEndpointParams
{
    Url = "https://mi-plataforma.mx/webhooks/fiscalisto",
    Events = new()
    {
        "invoice.stamped",
        "invoice.failed",
        "invoice.canceled",
    },
});
```

La respuesta trae el `secret` (`whsec_…`) una sola vez: guárdelo en su servidor. Sin `events` recibe todos. También puede agregarlo en **Desarrolladores › Webhooks**. Cada modo tiene sus propios webhooks: uno creado con una llave `fk_test_` solo recibe eventos de pruebas.

La dirección debe ser `https` y pública. No se siguen redirecciones: use la dirección final. Para probar en su computadora no hace falta un túnel: `npx fiscalisto listen --forward-to http://localhost:3000/webhooks/fiscalisto` le reenvía los eventos de su cuenta de pruebas firmados igual que los de verdad, y `npx fiscalisto trigger invoice.stamped` provoca uno (vea [CLI](https://fiscalisto.com/docs/api/cli#webhooks-en-su-computadora)). Si prefiere registrar un endpoint de verdad, abra un túnel (`cloudflared tunnel --url http://localhost:3000` o `ngrok http 3000`) y registre la dirección `https` que le da.

## Verificar la firma

Cada evento llega como `POST` con tres encabezados, según [Standard Webhooks](https://www.standardwebhooks.com):

| Encabezado | Qué es |
| --- | --- |
| `webhook-id` | El id del evento (`evt_…`). Es el mismo en cada reintento. |
| `webhook-timestamp` | Cuándo se mandó este intento, en segundos desde 1970. |
| `webhook-signature` | `v1,` y la firma HMAC-SHA256 en base64. Puede traer varias separadas por espacio. |

Verifique siempre la firma antes de confiar en el evento: cualquiera puede mandar un `POST` a su dirección. Con los [SDKs](https://fiscalisto.com/docs/api/sdk#webhooks) es una línea y no necesita la llave de la API: `verifyWebhook(cuerpo, encabezados, secreto)` en TypeScript, `verify_webhook(…)` en Python, `Webhook::verify(…)` en PHP, `fiscalisto.VerifyWebhook(…)` en Go, `Webhooks.verify(…)` en Java y `FiscalistoClient.Webhooks.Verify(…)` en C#. En otro lenguaje, la librería `standardwebhooks` hace lo mismo (Ruby, Rust y los demás).

```ts title="Node.js (Express) con el SDK"
import express from "express";
import { verifyWebhook } from "fiscalisto";

const app = express();

// El cuerpo tal como llegó, sin convertirlo a JSON: la firma es de esos bytes.
app.post("/webhooks/fiscalisto", express.raw({ type: "application/json" }), async (req, res) => {
  let event;
  try {
    event = await verifyWebhook(req.body, req.headers, process.env.FISCALISTO_WEBHOOK_SECRET);
  } catch {
    return res.status(400).send("Firma inválida");
  }
  res.sendStatus(200); // Responda rápido; procese después.
  procesar(event);
});
```

```python title="Python (Flask) con el SDK"
import os
from flask import Flask, request
from fiscalisto import FiscalistoWebhookError, verify_webhook

app = Flask(__name__)

@app.post("/webhooks/fiscalisto")
def fiscalisto():
    try:
        event = verify_webhook(
            request.get_data(), request.headers, os.environ["FISCALISTO_WEBHOOK_SECRET"]
        )
    except FiscalistoWebhookError:
        return "Firma inválida", 400
    procesar(event)
    return "", 200
```

### Sin librería

La firma es el HMAC-SHA256 de `{webhook-id}.{webhook-timestamp}.{cuerpo}` con su secreto sin el prefijo `whsec_`, decodificado de base64:

```python title="verificar.py"
import base64, hashlib, hmac, json, time

def verificar(secreto, headers, cuerpo: bytes):
    msg_id = headers["webhook-id"]
    timestamp = headers["webhook-timestamp"]
    if abs(time.time() - int(timestamp)) > 300:
        raise ValueError("Evento viejo: puede ser una repetición maliciosa")
    llave = base64.b64decode(secreto.removeprefix("whsec_"))
    firmado = f"{msg_id}.{timestamp}.".encode() + cuerpo
    esperada = base64.b64encode(hmac.new(llave, firmado, hashlib.sha256).digest()).decode()
    for firma in headers["webhook-signature"].split(" "):
        version, _, valor = firma.partition(",")
        if version == "v1" and hmac.compare_digest(valor, esperada):
            return json.loads(cuerpo)
    raise ValueError("Firma inválida")
```

Rechace los eventos con más de 5 minutos de diferencia en `webhook-timestamp`: así nadie puede repetirle uno viejo.

## Probar sus webhooks

Con una llave `fk_test_` todo pasa en pruebas y sus webhooks de pruebas reciben los eventos de verdad:

1. Agregue un webhook de pruebas con la dirección de su túnel (vea arriba).
2. Timbre una factura con su llave de pruebas: con `curl`, con un SDK o con **Probar** en la [referencia](https://fiscalisto.com/docs/api/referencia/facturas/create-invoice). Le llega `invoice.stamped`.
3. Para `invoice.failed`, timbre a uno de los [RFC de prueba](https://fiscalisto.com/docs/api/pruebas-y-produccion#provocar-cada-rechazo-del-sat), como `NOM010101FK9`; para `invoice.canceled`, cancele la factura con `"motive": "02"`.
4. En **Desarrolladores › Webhooks** vea cada entrega con lo que contestó su servidor, y reenvíela cuantas veces quiera mientras corrige su código. **Desarrolladores › Eventos** tiene todos los eventos, con lo que se mandó y cada entrega.

### Un evento de prueba, sin timbrar

Para probar su código con cualquier evento, mande uno de prueba a su webhook. Le llega en ese momento, una sola vez, firmado como los reales, con un objeto de ejemplo (sus id no existen), aunque el webhook no escuche ese tipo:

```bash tab="cURL"
curl https://api.fiscalisto.com/v1/webhook_endpoints/whe_8Kq2ZtR4mNw1Lp0sXc7V/test \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "invoice.failed" }'
```

```ts tab="TypeScript"
import { Fiscalisto } from "fiscalisto";

// La llave viene de FISCALISTO_API_KEY.
const fiscalisto = new Fiscalisto();

const webhookTest = await fiscalisto.webhookEndpoints.test("whe_8Kq2ZtR4mNw1Lp0sXc7V", { type: "invoice.failed" });
```

```python tab="Python"
from fiscalisto import Fiscalisto

# La llave viene de FISCALISTO_API_KEY.
fiscalisto = Fiscalisto()

webhook_test = fiscalisto.webhook_endpoints.test("whe_8Kq2ZtR4mNw1Lp0sXc7V", {"type": "invoice.failed"})
```

```php tab="PHP"
<?php

use Fiscalisto\Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
$fiscalisto = new Fiscalisto();

$webhookTest = $fiscalisto->webhookEndpoints->test('whe_8Kq2ZtR4mNw1Lp0sXc7V', ['type' => 'invoice.failed']);
```

```go tab="Go"
package main

import (
	"context"
	"fmt"
	"log"

	fiscalisto "fiscalisto.com/go"
)

func main() {
	// La llave viene de FISCALISTO_API_KEY.
	client, err := fiscalisto.NewClient("")
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()

	webhookTest, err := client.WebhookEndpoints.Test(ctx, "whe_8Kq2ZtR4mNw1Lp0sXc7V", &fiscalisto.TestWebhookEndpointParams{
		Type: fiscalisto.String("invoice.failed"),
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", *webhookTest)
}
```

```java tab="Java"
import com.fiscalisto.FiscalistoClient;
import com.fiscalisto.model.TestWebhookEndpointParams;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

var webhookTest = fiscalisto.webhookEndpoints().test("whe_8Kq2ZtR4mNw1Lp0sXc7V", new TestWebhookEndpointParams().setType("invoice.failed"));
System.out.println(webhookTest);
```

```csharp tab="C#"
using Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

var webhookTest = await fiscalisto.WebhookEndpoints.TestAsync("whe_8Kq2ZtR4mNw1Lp0sXc7V", new TestWebhookEndpointParams { Type = "invoice.failed" });
```

La respuesta trae el evento que se mandó y lo que contestó su servidor:

```json
{
  "object": "webhook_test",
  "event": { "id": "evt_0PKhIdhYuAGwzmMQYfkc", "object": "event", "type": "invoice.failed", "...": "..." },
  "delivery": { "ok": true, "status": 200, "error": null, "response_body": "{\"received\":true}", "duration_ms": 182 }
}
```

Sin `type` manda `invoice.stamped`. En el panel es **Mandar evento de prueba**, en cada webhook. El evento de prueba no sale en `GET /v1/events`, pero sí en las entregas del webhook, y puede reenviarlo.

## Responder

- Conteste un código `2xx` en menos de 15 segundos. Cualquier otro código, una redirección o no contestar a tiempo cuenta como falla.
- Conteste primero y procese después (en una cola o en segundo plano), para no pasar de los 15 segundos.
- Un evento puede llegar más de una vez. Guarde el `webhook-id` de los que ya procesó e ignore los repetidos.
- Los eventos pueden llegar en otro orden. Para saber el estado actual de un comprobante use el `status` del objeto que trae el evento o consúltelo con `GET`.

## Reintentos

Si su servidor falla, reintentamos el mismo evento durante un día: a los 5 segundos, 5 minutos, 30 minutos, 2 horas, 5 horas, 10 horas y 10 horas. Cada intento trae el mismo `webhook-id` y un `webhook-timestamp` nuevo.

Un webhook que falla todos los reintentos queda en `status: "failing"`. Si sigue así 5 días, se apaga (`"disabled"`) y deja de recibir eventos; vuelva a encenderlo con [`POST /v1/webhook_endpoints/{id}`](https://fiscalisto.com/docs/api/referencia/webhooks/update-webhook-endpoint) y `"disabled": false`, o en el panel. En **Desarrolladores › Webhooks** cada webhook muestra sus últimos 7 días (entregados, fallidos, tasa de error y respuesta promedio) y sus entregas con lo que contestó su servidor, y puede reenviar cualquiera. **Reenviar** es un intento aparte, ahora: no cambia los reintentos automáticos, y si llega, ya no se reintenta.

Si se le pasaron eventos, [`GET /v1/events`](https://fiscalisto.com/docs/api/referencia/eventos/list-events) tiene los de los últimos 30 días; filtre por `type` (uno, o todos los de un recurso con un comodín: `invoice.*`), por `object` (el id del comprobante) o con `delivery_success=false` por los que algún webhook no recibió:

```bash tab="cURL"
curl "https://api.fiscalisto.com/v1/events?type=invoice.*&delivery_success=false" \
  -H "Authorization: Bearer $FISCALISTO_API_KEY"
```

```ts tab="TypeScript"
import { Fiscalisto } from "fiscalisto";

// La llave viene de FISCALISTO_API_KEY.
const fiscalisto = new Fiscalisto();

for await (const item of fiscalisto.events.list({ type: "invoice.*", delivery_success: false })) {
  console.log(item.id);
}
```

```python tab="Python"
from fiscalisto import Fiscalisto

# La llave viene de FISCALISTO_API_KEY.
fiscalisto = Fiscalisto()

for item in fiscalisto.events.list({"type": "invoice.*", "delivery_success": False}):
    print(item["id"])
```

```php tab="PHP"
<?php

use Fiscalisto\Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
$fiscalisto = new Fiscalisto();

foreach ($fiscalisto->events->list([
    'type' => 'invoice.*',
    'delivery_success' => false,
]) as $item) {
    echo $item['id'], "\n";
}
```

```go tab="Go"
package main

import (
	"context"
	"fmt"
	"log"

	fiscalisto "fiscalisto.com/go"
)

func main() {
	// La llave viene de FISCALISTO_API_KEY.
	client, err := fiscalisto.NewClient("")
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()

	for item, err := range client.Events.ListAutoPaging(ctx, &fiscalisto.ListEventsParams{
		Type:            fiscalisto.String("invoice.*"),
		DeliverySuccess: fiscalisto.Bool(false),
	}) {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(item.ID)
	}
}
```

```java tab="Java"
import com.fiscalisto.FiscalistoClient;
import com.fiscalisto.model.ListEventsParams;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

for (var item : fiscalisto.events().listAutoPaging(new ListEventsParams().setType("invoice.*").setDeliverySuccess(false))) {
    System.out.println(item.getId());
}
```

```csharp tab="C#"
using Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

await foreach (var item in fiscalisto.Events.ListAutoPagingAsync(new ListEventsParams
{
    Type = "invoice.*",
    DeliverySuccess = false,
}))
    Console.WriteLine(item.Id);
```

## Cambiar el secreto

[`POST /v1/webhook_endpoints/{id}/roll_secret`](https://fiscalisto.com/docs/api/referencia/webhooks/roll-webhook-secret) le da un secreto nuevo. El anterior sigue firmando 24 horas: mientras tanto cada evento trae dos firmas, una con cada secreto, y la verificación pasa con cualquiera. Cambie su servidor al nuevo dentro de ese plazo. Para otro plazo mande `previous_secret_expires_in` en segundos; si el secreto se filtró, mande `0` y el anterior deja de firmar en ese momento.

```bash tab="cURL"
curl -X POST https://api.fiscalisto.com/v1/webhook_endpoints/whe_0q1Vmmw52sbDpmtsKD1E/roll_secret \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "previous_secret_expires_in": 0 }'
```

```ts tab="TypeScript"
import { Fiscalisto } from "fiscalisto";

// La llave viene de FISCALISTO_API_KEY.
const fiscalisto = new Fiscalisto();

const webhookEndpoint = await fiscalisto.webhookEndpoints.rollSecret("whe_0q1Vmmw52sbDpmtsKD1E", { previous_secret_expires_in: 0 });
```

```python tab="Python"
from fiscalisto import Fiscalisto

# La llave viene de FISCALISTO_API_KEY.
fiscalisto = Fiscalisto()

webhook_endpoint = fiscalisto.webhook_endpoints.roll_secret("whe_0q1Vmmw52sbDpmtsKD1E", {"previous_secret_expires_in": 0})
```

```php tab="PHP"
<?php

use Fiscalisto\Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
$fiscalisto = new Fiscalisto();

$webhookEndpoint = $fiscalisto->webhookEndpoints->rollSecret('whe_0q1Vmmw52sbDpmtsKD1E', ['previous_secret_expires_in' => 0]);
```

```go tab="Go"
package main

import (
	"context"
	"fmt"
	"log"

	fiscalisto "fiscalisto.com/go"
)

func main() {
	// La llave viene de FISCALISTO_API_KEY.
	client, err := fiscalisto.NewClient("")
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()

	webhookEndpoint, err := client.WebhookEndpoints.RollSecret(ctx, "whe_0q1Vmmw52sbDpmtsKD1E", &fiscalisto.RollWebhookSecretParams{
		PreviousSecretExpiresIn: fiscalisto.Int64(0),
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(webhookEndpoint.ID)
}
```

```java tab="Java"
import com.fiscalisto.FiscalistoClient;
import com.fiscalisto.model.RollWebhookSecretParams;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

var webhookEndpoint = fiscalisto.webhookEndpoints().rollSecret("whe_0q1Vmmw52sbDpmtsKD1E", new RollWebhookSecretParams().setPreviousSecretExpiresIn(0));
System.out.println(webhookEndpoint.getId());
```

```csharp tab="C#"
using Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

var webhookEndpoint = await fiscalisto.WebhookEndpoints.RollSecretAsync("whe_0q1Vmmw52sbDpmtsKD1E", new RollWebhookSecretParams { PreviousSecretExpiresIn = 0 });
```
