Docs
BasicsAPI Reference
BasicsAPI Reference
  1. Integration Guide
  • Integration Guide
    • Payment Links
    • Charges
    • Webhooks
  • PaymentLinks
    • Create payment link
      POST
    • List payment links
      GET
    • Get payment link
      GET
    • Cancel payment link
      POST
  • Charges
    • List charges
      GET
    • Get charge
      GET
  • Webhooks
    • Payment Link
    • Charge
  1. Integration Guide

Webhooks

Woldy uses webhooks to notify your server about events — for example, when a payment link is paid, a charge fails, or a subscription goes past due. You create an HTTPS endpoint that Woldy sends POST requests to.

Quick Start#

1.
Create an HTTPS endpoint to receive webhook requests
2.
Register a webhook in the dashboard under Settings → Branch → Webhooks, and pick the event types you want
3.
Save the Public Key shown in the dashboard — you need it to verify signatures
4.
Verify the signature of every incoming request before trusting its contents
5.
Return HTTP 2xx in response to each request
6.
Use Settings → Branch → Webhooks → Send test event (webhook.test) to confirm delivery and signature verification end-to-end before going live

Setup#

Webhook creation and management is available through the dashboard under Settings → Branch → Webhooks — this is not part of the API-key–authenticated REST surface documented elsewhere in this reference.
When creating a webhook, specify:
URL — your server's HTTPS endpoint (HTTP is not supported; private IPs, localhost, and cloud metadata addresses are rejected)
Events — the event types to subscribe to (see the full catalog below)
Description — optional
After creation, the dashboard shows a Public Key (Ed25519, base64). This key is the same for every webhook on your account — save it. It is shown again any time you view or edit a webhook, so losing it isn't fatal, but treat it as long-lived integration config rather than a one-time secret.
Limits: max 10 webhooks per branch, HTTPS only.

Event Catalog#

Every event shares the same outer envelope (see below); only type and data change. data has one shape per group — the individual event types within a group only differ in status/timing fields, not structure. Each group below is documented on a single page — pick the matching example from the type field's examples to see that specific event's payload.

Payment Link#

Event type
payment_link.created
payment_link.processing
payment_link.attempt_failed
payment_link.cancelled
payment_link.completed
payment_link.expired

Charge#

A charge is one payment attempt. A single payment link can produce several charges (e.g. one failed attempt followed by a successful one).
Event type
charge.created
charge.completed
charge.failed

Subscription#

Event type
subscription.created
subscription.activated
subscription.cancelled
subscription.cancellation_scheduled
subscription.cancellation_revoked
subscription.expired
subscription.past_due
subscription.recovered
subscription.payment_succeeded — only event type where data.charge is populated (with payment_failed)
subscription.payment_failed — only event type where data.charge is populated (with payment_succeeded)
subscription.paused
subscription.resumed
subscription.allowance_low
subscription.allowance_revoked
subscription.allowance_expiring

Subscription Plan#

Event type
subscription_plan.created
subscription_plan.updated
subscription_plan.paused
subscription_plan.resumed
subscription_plan.archived

Test#

Event type
webhook.test — sent by "Send test event" in the dashboard, not a real business event

Incoming Request#

Each webhook is a POST request with Content-Type: application/json.

Headers#

HeaderValue
X-Woldy-SignatureEd25519 signature, base64 Standard Encoding (RFC 4648, with = padding)
X-Woldy-Signature-VersionAlways ed25519-v1
X-Woldy-Event-IDEvent UID — wev_... (base64url-encoded UUID). Not a raw UUID — use this exact string, prefix included, when verifying the signature
X-Woldy-Event-TypeEvent type, e.g. charge.completed
X-Woldy-TimestampUnix timestamp of the request (integer, seconds)
Content-Typeapplication/json
User-AgentWoldy-Webhooks/1.0

Envelope#

Every request body has the same outer shape:
{
  "id": "wev_VQ6EA4np...",
  "branch_id": "cb_VQ6EA4np...",
  "webhook_id": "wh_VQ6EA4np...",
  "type": "charge.completed",
  "api_version": "2024-01-01",
  "created_at": 1737720000,
  "data": {
    "...": "shape depends on the event group — see the Event Catalog above"
  }
}
id — same value as X-Woldy-Event-ID (with the wev_ prefix)
branch_id — your branch UID (cb_...)
webhook_id — the webhook subscription that delivered this event (wh_...)
created_at — Unix timestamp, seconds
For the exact fields inside data for each event group, follow the links in the Event Catalog above.

Signature Verification#

Always verify the signature before processing event data.

How it works#

Woldy signs every request using Ed25519. The signed string is:
{timestamp}.{eventID}.{bodyJSON}
Where:
timestamp — value from X-Woldy-Timestamp
eventID — value from X-Woldy-Event-ID (the full wev_... string, exactly as received — not a raw UUID)
bodyJSON — the exact raw bytes of the HTTP request body
The signature covers the entire request body. Always verify against the raw body bytes — never re-serialize parsed JSON.

Steps#

1.
Extract X-Woldy-Timestamp, X-Woldy-Event-ID, X-Woldy-Signature from headers
2.
Check freshness: |now - timestamp| ≤ 300 seconds
3.
Build: "{timestamp}.{eventID}.{bodyJSON}"
4.
Decode X-Woldy-Signature from base64 Standard Encoding
5.
Verify the Ed25519 signature with the Public Key from the dashboard
Node.js
Python
Go

Retry Policy#

If your server returns an error (4xx, 5xx) or does not respond within the configured request timeout:
AttemptDelay after previous attempt
1Immediately
2~1 minute
3~5 minutes
4~30 minutes
5+~2 hours, repeating (±20% jitter applied to every delay)
Delivery is retried up to a server-configured attempt limit, after which it's marked FAILED. You can inspect delivery history and manually retry a specific delivery from the dashboard.

Best Practices#

Respond quickly — return 200 immediately, process asynchronously.
Handle duplicates — the same event may be delivered more than once (e.g. after a retry that actually succeeded but the ack was lost). Use the event ID (X-Woldy-Event-ID / body id) for deduplication.
Always verify — never process a webhook without verifying the signature.
Don't rely on delivery order — deliveries for different events can arrive out of order. If ordering matters, use the resource's own status/timestamp fields (e.g. re-fetch the current state via the REST API) rather than assuming event arrival order reflects it.

Troubleshooting#

Not receiving events — check the webhook's status in the dashboard, confirm you subscribed to the correct event type, and check the webhook's delivery history for failed attempts and their error messages.
Signature error:
Use the Public Key currently shown in the dashboard for this webhook
Sign only the exact raw request body bytes — do not re-serialize parsed JSON
Timestamp must be within ±5 minutes
Decode the signature from base64 Standard Encoding (with = padding)
The Public Key is raw 32-byte Ed25519, not X.509/SPKI
Timeout — respond 200 immediately, process asynchronously; don't do slow work (DB writes, outbound calls) before acknowledging.
Modified at 2026-08-19 09:43:07
Previous
Charges
Next
Create payment link
Built with