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

# Webhooks

> Receive Infi events, verify signatures and inspect delivery.

Payments are asynchronous. A successful API request or a checkout redirect is
not proof that funds were received. Your backend should consume Infi events,
not separate event implementations for each provider.

## In the sandbox

Use polling while testing:

```ts theme={null}
const paid = await infi.pay.waitForPaid({ slug, invoiceId });
const invoice = await infi.invoices.get(invoiceId);
```

`waitForPaid` defaults to a three-second interval and a ten-minute timeout.
It returns `true` when paid and `false` on timeout.

Sandbox webhook registration currently returns `503 secret_store_unavailable`
when signing secrets cannot be stored. Listing endpoints and deliveries still
works; use polling for the sandbox payment loop.

## Register a production endpoint

```ts theme={null}
const endpoint = await infi.webhooks.create({
  url: "https://example.com/api/webhooks/infi",
  events: ["payment.confirmed", "invoice.paid"]
});
```

The signing secret is returned only on creation. Store it in your secret manager
as `INFI_WEBHOOK_SECRET`. Do not expose it to the browser or commit it.

## Verify the signature

Read the raw request body before parsing JSON:

```ts theme={null}
import { verifyWebhook, InfiError } from "@beinfi/sdk";
import type { PaymentConfirmedData } from "@beinfi/sdk";

export async function POST(req: Request) {
  const body = await req.text();
  try {
    const event = verifyWebhook<PaymentConfirmedData>(
      {
        id: req.headers.get("x-webhook-id")!,
        timestamp: req.headers.get("x-webhook-timestamp")!,
        signature: req.headers.get("x-webhook-signature")!,
        eventType: req.headers.get("x-webhook-event-type")!,
        body
      },
      process.env.INFI_WEBHOOK_SECRET!
    );

    if (event.type === "payment.confirmed") {
      // Persist and process the event idempotently before acknowledging.
      // Use event.data.invoiceId to reconcile it with your order.
    }
    return new Response("ok");
  } catch (error) {
    if (error instanceof InfiError) {
      return new Response(error.code, { status: 400 });
    }
    throw error;
  }
}
```

This skeleton only verifies and acknowledges the request. Add your durable event
handling before using it in production.

The event type comes from `X-Webhook-Event-Type`. The payload is JSON; amounts
and UUIDs are strings. Optional fields may be absent.

## Common events

| Event                     | Meaning                                  |
| ------------------------- | ---------------------------------------- |
| `payment.confirmed`       | A payment was confirmed                  |
| `payment.failed`          | A payment attempt failed                 |
| `payment.refunded`        | A refund was recorded                    |
| `payment.chargeback`      | A chargeback was recorded                |
| `invoice.finalized`       | An invoice is finalized and collectible  |
| `invoice.paid`            | The invoice is paid                      |
| `invoice.voided`          | The invoice was voided                   |
| `usage.threshold_reached` | A configured usage threshold was reached |

An invoice may involve multiple payments. Select the event that matches the
business operation you need to trigger.

## Handle repeated delivery

Delivery is at least once. Make both event ingestion and the resulting business
effect idempotent. Persist the event and schedule its processing durably; use a
transaction or an outbox when coordinating database state with other effects.

Do not make every effect share one invoice-only key: a payment, a refund and a
chargeback for the same invoice are different operations. Conversely, do not
grant access twice just because two distinct events describe the same purchase.

Return a success response only after the event has been handled or durably
accepted for processing. Failed delivery may be retried.

## Inspect delivery

```ts theme={null}
const deliveries = await infi.webhooks.listDeliveries();
```

Check [the SDK](https://beinfi.com/en/sdk) for related payment reads and
[the HTTP API](https://beinfi.com/en/api-http) for endpoint paths.
