Webhooks
PayLinkr can send signed webhook events whenever invoice state changes. Webhooks are intended for PRO accounts that want fulfillment, entitlement, or accounting workflows to run automatically.
01 Setup
- Open your dashboard webhook settings or use the PRO webhook API routes.
- Set a publicly reachable HTTPS endpoint that accepts POST requests.
- Store the webhook signing secret securely in your application.
- Send a test event before relying on live delivery.
02 Event types
| Event | Meaning |
|---|---|
| invoice.created | A new invoice was created and is now available for payment. |
| invoice.paid | The invoice has been fully satisfied and marked paid. |
| invoice.partially_paid | A payment was matched, but the invoice still has an outstanding balance. |
| invoice.underpaid | The platform classified the current payment state as underpaid. |
| invoice.overpaid | The total amount received is above the expected amount. |
| invoice.expired | The invoice expired before a valid final settlement state was reached. |
Depending on how you use invoice actions and settlement controls, your system may see more than one event over the life of a single invoice.
03 Payload shape
Webhooks are delivered as JSON POST requests. The payload includes invoice identifiers, settlement information, and timestamps so your application can make deterministic decisions.
{
"event": "invoice.paid",
"invoiceId": "clxxxxxxxxxxxxx",
"publicId": "a1b2c3d4e5f6g7h8",
"status": "paid",
"title": "Consulting Deposit",
"expectedAmount": "250.00",
"receivedAmount": "250.00",
"asset": "USDC",
"paymentNetwork": "STELLAR",
"memo": "PLR-A1B2C3D4",
"reference": "PLR-A1B2C3D4",
"referenceLabel": "Memo",
"destinationAddress": "GABC...XYZ",
"orderId": "ORD-9001",
"createdAt": "2026-04-03T10:00:00.000Z",
"paidAt": "2026-04-03T10:05:32.000Z",
"expiresAt": "2026-04-04T10:00:00.000Z",
"timestamp": "2026-04-03T10:05:33.000Z"
}04 Signature verification
Each request includes an X-PayLinkr-Signature header in the format sha256=<digest>. Compute an HMAC SHA-256 digest over the raw request body using your webhook secret and compare it using a timing-safe method.
import crypto from 'crypto'
export function verifyPayLinkrWebhook(rawBody: string, signature: string, secret: string) {
const expected =
'sha256=' +
crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex')
try {
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
} catch {
return false
}
}05 Headers
| Header | Purpose |
|---|---|
| X-PayLinkr-Signature | HMAC SHA-256 signature for the raw request body. |
| X-PayLinkr-Event | The logical event type, such as invoice.paid. |
| X-PayLinkr-Delivery | Unique delivery identifier for that specific attempt. |
| Content-Type | application/json |
| User-Agent | PayLinkr-Webhook/1.0 |
06 Delivery and retries
Treat webhook delivery as at-least-once. Your handler should respond quickly with a 2xx status code, queue any heavier work, and make downstream processing idempotent.
| Recommended practice | Why it matters |
|---|---|
| Return 2xx quickly | Avoid unnecessary retries caused by long-running business logic. |
| Use event plus invoice ID as an idempotency key | The same invoice may be retried or re-sent across delivery attempts. |
| Log delivery IDs | Lets you reconcile a specific webhook attempt with PayLinkr delivery logs. |
| Store raw payloads during rollout | Makes debugging easier while your integration hardens. |
07 Operating webhooks in production
- - Rotate webhook secrets when operators change or a secret may have leaked.
- - Use the dashboard or webhook API to send test events before enabling live fulfillment.
- - Review delivery logs when a customer says a paid invoice did not unlock service in your app.
- - Combine webhook handling with the API if you need to re-check invoice state before committing fulfillment.