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

# Webhook Handling

> Handle SolvaPay webhooks in your TypeScript application.

For the complete webhook guide — setup, signature verification, event catalog, payload schemas,
and best practices — see the central [Webhooks documentation](/webhooks).

## Quick Reference

```typescript theme={null}
import { verifyWebhook } from '@solvapay/server'

const event = verifyWebhook({
  body: rawBodyString,
  signature: request.headers.get('sv-signature')!,
  secret: process.env.SOLVAPAY_WEBHOOK_SECRET!,
})

switch (event.type) {
  case 'payment.succeeded':
    // fulfil the order
    break
  case 'purchase.created':
    // grant access
    break
  case 'purchase.cancelled':
    // revoke access
    break
}
```

### Edge Runtimes

```typescript theme={null}
import { verifyWebhook } from '@solvapay/server/edge'

const event = await verifyWebhook({
  body: rawBodyString,
  signature: svSignatureHeader,
  secret: process.env.SOLVAPAY_WEBHOOK_SECRET!,
})
```

### Fetch runtimes (Deno, Cloudflare Workers, Supabase Edge)

For web-standards fetch runtimes, `@solvapay/server/fetch` provides a `solvapayWebhook` factory that handles the full lifecycle (read body, verify signature, parse, respond):

```typescript theme={null}
// supabase/functions/solvapay-webhook/index.ts
import { solvapayWebhook } from '@solvapay/server/fetch'

Deno.serve(solvapayWebhook({
  onEvent: async event => {
    // Handle events here
  },
}))
```

The factory reads `SOLVAPAY_WEBHOOK_SECRET` from the environment automatically. Use `verifyWebhook` from `@solvapay/server/edge` if you need manual control over the request lifecycle.

See the [Supabase Edge Functions guide](/sdks/typescript/guides/supabase-edge) for the complete setup.

## Event Types

SolvaPay emits **46 event types** across these domains. `event.type` is fully typed as
`WebhookEventType`, so your editor autocompletes every name and narrows `event.data.object`
in a `switch`.

| Domain                           | Examples                                                                                                                                                                                 |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Customer**                     | `customer.created`, `customer.updated`, `customer.deleted`                                                                                                                               |
| **Purchase & subscription**      | `purchase.created`, `purchase.activated`, `purchase.renewed`, `purchase.past_due`, `purchase.cancellation_scheduled`, `purchase.cancelled`, `purchase.plan_changed`, `purchase.refunded` |
| **Payments, refunds & disputes** | `payment.succeeded`, `payment.failed`, `payment.refunded`, `payment.refund_pending`, `payment.canceled`, `payment.disputed`, `payment.dispute_closed`                                    |
| **Payouts**                      | `payout.paid`, `payout.failed`                                                                                                                                                           |
| **Checkout**                     | `checkout_session.created`, `checkout_session.completed`, `checkout_session.expired`                                                                                                     |
| **Credits**                      | `customer.credit.topped_up`, `customer.credit.debited`, `customer.credit.low_balance`, `customer.credit.exhausted`, `customer.credit.granted`, `customer.credit.adjusted`                |
| **Usage & metering**             | `usage.charged`, `usage.recorded`, `usage.reset`                                                                                                                                         |
| **Catalog**                      | `product.created/updated/archived`, `plan.created/updated/archived`                                                                                                                      |

See the [central Webhooks documentation](/webhooks#event-types) for the full table and per-event payload schemas.

### Subscribing to specific events

Endpoints receive **all events by default**. To receive only a subset, set `enabledEvents`
when creating/updating the endpoint (empty = all), or choose **Selected events** in the
Console. See [Choosing which events to receive](/webhooks#choosing-which-events-to-receive).

## Side effects on Customer and Transaction records

Two SolvaPay-internal webhook handlers update derived data you may rely on:

* **Card mirror on `payment_intent.succeeded`.** When a payment succeeds, SolvaPay mirrors the card brand, last4, expMonth, and expYear onto the `Customer` record. `GET /v1/sdk/payment-method?customerRef=…` reads from this mirror, so the SDK's `usePaymentMethod` hook and `CurrentPlanCard` never round-trip to Stripe. The mirror is updated whenever a new successful payment lands, so the "payment method on file" always reflects the last working card.
* **Refund FX normalization.** For refunds against transactions with a non-unity exchange rate (e.g. EUR → USD), the refund record now stores both the original-currency amount and the USD equivalent computed from the source transaction's stored `exchangeRate`. This keeps credit balances and revenue reports consistent after cross-currency refunds.

Both behaviors are idempotent and safe to replay.

See [Webhooks documentation](/webhooks) for full payload schemas and integration examples.
