Skip to main content

Overview

SolvaPay webhooks push event notifications to your application via HTTP POST requests whenever significant business events occur — a payment succeeds, a purchase is cancelled, a customer is created, and so on. Instead of polling the API, you register an endpoint URL and SolvaPay delivers a signed JSON payload for each event. Your server verifies the signature, processes the event, and returns a 2xx response.

How it works


Quick Start

1. Install the SDK

2. Create a webhook endpoint

In SolvaPay Console, go to Settings -> Webhooks and add your endpoint URL:
Copy the signing secret (whsec_…) — you will need it to verify signatures.

3. Handle incoming webhooks

Always use request.text() (Next.js) or express.raw() (Express) so the body is available as a raw string. If the body has been JSON-parsed before verification, the signature check will fail.

Payload Format

Every webhook POST contains a JSON body with this structure:

HTTP Headers


Signature Verification

Every webhook includes an SV-Signature header. You must verify it to confirm the request genuinely came from SolvaPay and has not been tampered with.

How the signature is computed

  1. SolvaPay takes the current Unix timestamp and the raw JSON body.
  2. It concatenates them as "{timestamp}.{rawBody}".
  3. It computes an HMAC-SHA256 of that string using your signing secret (including the whsec_ prefix) as the key.
  4. The result is sent as t={timestamp},v1={hex_digest}.

Using the SDK

verifyWebhook returns a typed WebhookEvent object with full IntelliSense for event types and payload fields:
verifyWebhook throws a SolvaPayError if the signature is invalid or older than 5 minutes.

Edge Runtimes

For Vercel Edge Functions, Cloudflare Workers, or Deno Deploy, import from the edge entry point. It uses the Web Crypto API and returns a Promise:

Manual verification (without the SDK)


Event Types

SolvaPay emits 47 event types spanning the full lifecycle — from customer and purchase creation through payments, refunds, disputes, payouts, credits, metered usage, and catalog changes. The canonical list is also available programmatically from GET /v1/sdk/webhooks/event-types and is what powers both the SDK WebhookEventType type and the event multiselect in the Console.
By default an endpoint receives all events. To narrow it down, see Choosing which events to receive.

Customer Events

Purchase & Subscription Events

The complete purchase/subscription state machine:

Payment, Refund & Dispute Events

Payout Events

Checkout Session Events

Credit Events

Usage & Metering Events

Catalog Events

Reactivation: undoing a pending cancellation via reactivateRenewal emits purchase.reactivated.Plan switching: calling activatePlan with a different plan emits purchase.expired for the old purchase, purchase.created for the new one, and purchase.plan_changed.

Choosing which events to receive

By default, every endpoint receives all event types. You can subscribe an endpoint to a specific subset so your handler only gets the events it cares about.

In the Console

Under Settings → Webhooks, expand an endpoint and pick Selected events to choose specific event types (grouped by category). Leave it on All events to receive everything — including any new event types SolvaPay adds later.

Via the API

Pass enabledEvents when creating or updating an endpoint. An empty or omitted array means “all events”; a non-empty array subscribes to only those types.
Subscribing to a subset is purely a delivery filter — it does not change the payload shape or signing. Endpoints left on “all events” automatically receive new event types as they are introduced, so you never miss a future event.

Event Payloads

payment.succeeded

payment.failed

payment.refunded

payment.refund_failed

purchase.created

purchase.updated

purchase.cancelled

purchase.expired

purchase.suspended

customer.created

This payload can include a product field with a product reference. SolvaPay only populates product when the customer is created through the no-code MCP integration’s hosted OAuth for a product-linked client. To learn more about SDK customer syncing, see /sdks/typescript/setup/core-concepts.

customer.updated

customer.deleted

The customer.deleted payload contains only the customer id and a created timestamp. Other fields are not included since the record has been removed.

Customer sync with the TypeScript SDK

When you integrate with the TypeScript SDK (instead of the no-code MCP integration’s hosted OAuth), sync your users with ensureCustomer or the Next.js syncCustomer helper. This flow is idempotent. SolvaPay first looks up a customer by externalRef, then creates one if none exists. When a new customer is created this way, SolvaPay sends a customer.created webhook event with product set to null.
For implementation details, see /sdks/typescript/setup/core-concepts and /sdks/typescript/guides/nextjs.

checkout_session.created

checkout_session.completed and checkout_session.expired carry the same object shape, with status set to used and expired respectively.

Purchase lifecycle events

All purchase.* events share the purchase object shape shown under purchase.created; status reflects the new state. Two events add extra context:

Dispute & payout events

Credit events

credits is the running balance after the movement; amount is the signed delta. customer.credit.low_balance and customer.credit.exhausted fire automatically as the balance crosses the warning threshold and zero.
paymentIntentId is null when the charge was declined before a payment intent was created. Use this event to notify the customer to update their payment method — after repeated declines the auto-recharge config’s status flips to failed and no further charges are attempted until the card is updated.

Usage & metering events

usage.reset fires when counters roll over for a new billing period.

Catalog events


Best Practices

Never process a webhook without checking SV-Signature. Without verification, any third party could forge requests to your endpoint.
The SV-Delivery header is unique per delivery attempt. Store processed delivery IDs and skip duplicates to avoid processing the same event twice.
Return a 2xx as quickly as possible. If your handler needs to do heavy processing (e.g. sending emails, updating external systems), acknowledge the webhook immediately and move the work to a background queue.
Log and return 200 for event types you don’t recognise. This way the delivery is marked successful and SolvaPay won’t retry it.
Signature verification requires the exact body bytes. Always read the body as a raw string (request.text() or express.raw()) before parsing it as JSON.

Retry Schedule

If your endpoint returns a non-2xx status code or times out, SolvaPay retries delivery with exponential backoff: After 12 failed attempts the endpoint is automatically disabled. You can re-enable it from SolvaPay Console.

Testing Locally

Using ngrok

Sending a test event

Use the dashboard test button, or call the API directly:
This sends a payment.succeeded test event to the endpoint so you can verify your handler and signature verification are working correctly.