Webhooks
Paid can send HTTP POST requests to your system whenever important billing events happen. You configure webhooks in the Paid app under Settings > Webhooks, or manage them programmatically through the API.
This page covers:
- Which webhook events Paid supports today
- What the webhook payloads look like
- How to configure and manage webhooks
- How to test deliveries
Prerequisites
- Admin access to Settings > Webhooks or a Paid API key for programmatic webhook management.
- A publicly reachable HTTPS endpoint that can accept
POSTrequests from Paid. - Access to the raw request body in your web server so you can verify
x-webhook-signaturebefore parsing JSON. - A place to store the webhook signing secret securely, such as an environment variable or secret manager.
Supported events
Envelope format
Every webhook uses the same top-level envelope:
eventidentifies which webhook firedtimestampis the time Paid created the delivery in RFC 3339 formatisTestistruefor test deliveries sent from the UIdatacontains the event-specific payload, keyed by the primary payload key listed above
Verifying webhook signatures
Every delivery includes an HMAC-SHA256 signature in the x-webhook-signature header so you can verify the request came from Paid. Your organization has one signing secret, used by every webhook delivery regardless of event type. Generate or rotate it from Settings > Webhooks.
Header format
tis the delivery timestamp in milliseconds since the Unix epoch.sis the base64-encoded HMAC-SHA256 of the signed payload using your webhook’s signing secret as the key. (The trailing=is base64 padding. Do not strip it.)
The signed payload is the timestamp, a literal ., and the raw JSON request body, joined as one byte string:
Verify by recomputing the HMAC on your side and comparing in constant time. Reject deliveries where the timestamp is older than five minutes. This prevents replay of leaked deliveries.
Node.js example
Make sure your framework gives you the raw request body, not a re-serialized JSON object. Even whitespace differences break the signature.
Getting the raw body
Most web frameworks auto-parse JSON request bodies and discard the original bytes. The HMAC is computed over the literal bytes Paid sent, so a parsed-and-re-serialized object will not match. Below are minimal recipes for opting into raw-body access on the most common frameworks.
Express (Node): use express.raw() on the webhook route instead of express.json().
Next.js (App Router): use request.text(), not request.json().
Next.js (Pages Router): disable Next’s built-in body parser for the route.
Flask (Python): call request.get_data(), not request.get_json().
FastAPI (Python): await request.body() directly.
Django (Python): request.body is already the raw bytes. Exempt the route from CSRF since Paid is not the user’s browser.
Go (net/http): read the request body before decoding.
The general rule: if your framework auto-parses JSON, find the option to disable it on this route, or read the body before any parser runs.
Rotating the secret
There is one signing secret per organization. Every webhook delivery from your account is signed with the same key. Rotate from the UI (Settings > Webhooks > Rotate signing secret) or the API:
The response includes signingSecret exactly once. Store it before closing the response. Paid does not store it in a way you can read back.
Rotation invalidates the old secret on the next delivery. Update your receiver before rotating in production, or accept a short verification gap.
How delivery works
Each webhook event is configured independently. In the Paid UI you can:
- Open Settings > Webhooks
- Choose the event you want to receive
- Enter the receiver URL in your system
- Enable the webhook
- Use the Test button to send a sample payload
Paid sends webhook requests as HTTP POST calls. Your endpoint should:
- Accept JSON request bodies with
Content-Type: application/json - Return a
2xxresponse quickly after validating and enqueueing work - Treat deliveries as retriable and idempotent on your side
- Deduplicate using the business identifiers in the payload, not the delivery timestamp
Managing webhooks through the API
You can manage webhooks programmatically through the v2 API with an organization API key.
List all webhooks
Configure a webhook
Send a test delivery
Test deliveries only work after the webhook has a valid URL configured and
enabled is set to true.
Event payload examples
Payment succeeded
Field notes:
payment.statusis currentlypostedpayment.invoiceIdandpayment.invoiceNumbercan benullwhen the payment is not linked to an invoicepayment.externalPaymentIdcan benullwhen there is no upstream processor referencepaymentDateis the effective payment timestamp in RFC 3339 format
Payment failed
Field notes:
payment.statusis currentlyfailedfailureReasonis a human-readable failure message when one is availablefailureDateis when Paid recorded the failed attempt in RFC 3339 formatinvoiceId,invoiceNumber, andexternalPaymentIdcan benull
Credits depleted
Field notes:
remainingCreditsis0when this event firescreditsCurrencyIdcan benullif the depleted balance is not scoped to a credits currencysignalIdis the Paid signal that pushed the balance to zeroeventNameis the original signal event name that consumed the final credits
Overage incurred
Field notes:
eventTypeis one ofOverageUsageorOverageCreditthresholdis the included usage limit that was crossedcurrentUsageis the usage value observed when the overage condition was detectedcustomerId,customerName,planName,threshold, andcurrentUsagecan benullin edge cases
Implementation checklist
Before you enable a webhook in Paid, make sure your receiver:
- Accepts unauthenticated HTTPS
POSTrequests from Paid at a stable public URL - Parses the top-level envelope first, then branches on
event - Handles
nullvalues for optional fields likeinvoiceId,invoiceNumber,externalPaymentId,customerName,planName,threshold, andcurrentUsage - Returns a
2xxafter persisting or queueing the event - Ignores or separately labels
isTest: truedeliveries in your downstream systems - Safely ignores unknown
eventvalues for forward compatibility as new events are added
Testing from the Paid UI
Use the Test button in Settings > Webhooks to send a synthetic event to your receiver.
- Test deliveries use the same envelope shape as production deliveries
isTestis set totrue- IDs in the nested payload are synthetic test IDs such as
pay_test_*,cust_test_*,ola_test_*, orplan_test_* - Test payloads are intended to validate parsing and routing, not to represent real billing records in your system