Integration
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:
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
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:
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
const deliveries = await infi.webhooks.listDeliveries();Check the SDK for related payment reads and the HTTP API for endpoint paths.