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.
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 a2xx 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:whsec_…) — you will need it to verify signatures.
3. Handle incoming webhooks
Payload Format
Every webhook POST contains a JSON body with this structure:HTTP Headers
| Header | Example | Description |
|---|---|---|
SV-Signature | t=1740000000,v1=a1b2c3… | HMAC signature for verification |
SV-Event-Id | evt_abc123def456 | Unique event identifier |
SV-Delivery | dlv_xyz789 | Unique delivery attempt ID (use for idempotency) |
Content-Type | application/json | Always JSON |
User-Agent | SolvaPay/1.0 (+webhooks) | Identifies the sender |
Signature Verification
Every webhook includes anSV-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
- SolvaPay takes the current Unix timestamp and the raw JSON body.
- It concatenates them as
"{timestamp}.{rawBody}". - It computes an HMAC-SHA256 of that string using your signing secret (including the
whsec_prefix) as the key. - 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 aPromise:
Manual verification (without the SDK)
Event Types
SolvaPay emits the following business events. Only meaningful outcome events are sent — you will not receive events for internal CRUD operations on products, plans, or other configuration entities.Payment Events
| Event | Trigger | Description |
|---|---|---|
payment.succeeded | Stripe confirms payment | A payment has been successfully processed. |
payment.failed | Stripe reports failure | A payment attempt has failed. |
payment.refunded | Refund completed | A refund has been successfully processed. |
payment.refund_failed | Refund rejected | A refund attempt has failed. |
Purchase Events
| Event | Trigger | Description |
|---|---|---|
purchase.created | Purchase record created | A new purchase (subscription or one-time) has been created. Also fires when a plan switch creates the new purchase. |
purchase.updated | Status or fields change | A purchase has been modified (e.g. plan change, renewal, or reactivation — cancelledAt cleared and autoRenew restored). |
purchase.cancelled | Explicit cancel or period end | A purchase has been cancelled. |
purchase.expired | End date reached or plan switch | A purchase has expired naturally, or was expired by a plan switch replacing it with a new purchase. |
purchase.suspended | Payment overdue / trial expired | A purchase has been suspended due to non-payment. |
Reactivation: When a pending cancellation is undone via
reactivateRenewal, a purchase.updated event fires with cancelledAt cleared and autoRenew restored to true.Plan switching: When activatePlan is called with a different plan than the customer’s current purchase, two events fire: purchase.expired for the old purchase and purchase.created for the new one.Checkout Events
| Event | Trigger | Description |
|---|---|---|
checkout_session.created | Checkout session created via API or dashboard | A new checkout session has been created for a customer. |
Customer Events
| Event | Trigger | Description |
|---|---|---|
customer.created | Customer created via hosted auth, API, or SDK sync | A new customer record has been created. |
customer.updated | Customer details changed | A customer record has been updated. |
customer.deleted | Customer removed | A customer record has been deleted. |
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 aproduct field with a product reference.
SolvaPay only populates product when the customer is created through MCP Pay hosted OAuth
for a product-linked client.
To learn more about SDK customer syncing, see
/sdks/typescript/setup/core-concepts.
customer.updated
customer.deleted
Thecustomer.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 MCP Pay hosted OAuth), sync your users withensureCustomer 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.
/sdks/typescript/setup/core-concepts
and /sdks/typescript/guides/nextjs.
checkout_session.created
Best Practices
Always verify signatures
Always verify signatures
Never process a webhook without checking
SV-Signature. Without verification, any
third party could forge requests to your endpoint.Use the delivery ID for idempotency
Use the delivery ID for idempotency
The
SV-Delivery header is unique per delivery attempt. Store processed delivery IDs
and skip duplicates to avoid processing the same event twice.Respond within 10 seconds
Respond within 10 seconds
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.Handle unknown events gracefully
Handle unknown events gracefully
Log and return
200 for event types you don’t recognise. This way the delivery is
marked successful and SolvaPay won’t retry it.Keep the raw body intact
Keep the raw body intact
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:| Retry | Delay after previous |
|---|---|
| 1 | 5 minutes |
| 2 | 15 minutes |
| 3 | 1 hour |
| 4 | 6 hours |
| 5 | 24 hours |
| 6 | 48 hours |
| 7+ | 72 hours |
Testing Locally
Using ngrok
Sending a test event
Use the dashboard test button, or call the API directly:payment.succeeded test event to the endpoint so you can verify your handler
and signature verification are working correctly.