Real-time payment events. Cryptographically signed.
Seven event types. HMAC-SHA256 signatures with a 5-minute timestamp tolerance. Automatic retries up to 34 hours. Same delivery system that powers production merchants today.
What you receive.
Same shape, every event.
Lookup the full payment via GET /v1/payments/{id} after verifying the signature — the webhook payload is intentionally small, just enough to identify the payment without trusting client-side data.
{
"event": "payment.completed",
"timestamp": "2026-05-19T07:03:42Z",
"data": {
"paymentId": "8f3a9c2d-1b6e-4d8a-9c2e-3f4b5d6e7a8c",
"externalId": "order-1234"
}
}Verify before you trust the payload.
Every event includes an X-Pymstr-Signature header with the format t=<unix_timestamp>,v1=<hmac_signature>. Verify it against your webhook secret using HMAC-SHA256 over {timestamp}.{raw_body}.
Reject events where the timestamp drifts by more than 5 minutes (300 seconds) — protects against replays. Always use a constant-time comparison (crypto.timingSafeEqual / hmac.compare_digest) when comparing the signature.
import crypto from 'node:crypto';
function verifyPymstrSignature(rawBody, header, secret, tolerance = 300) {
// header format: "t=<unix_timestamp>,v1=<hmac_signature>"
const parts = Object.fromEntries(
header.split(',').map((p) => p.split('=', 2))
);
const timestamp = parseInt(parts.t, 10);
const signature = parts.v1;
// 1. Reject stale timestamps (5-minute tolerance by default)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > tolerance) return false;
// 2. Recompute HMAC-SHA256 over "<timestamp>.<raw_body>"
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
// 3. Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex'),
);
}Built-in retries. Widening backoff.
Return 200 within 10 seconds and the event is delivered. Otherwise, PYMSTR retries up to 7 times with increasing delays — as long as your endpoint comes back within the 34h 36m window, the event lands.
- 01
HTTPS endpoint
PYMSTR delivers webhooks only to HTTPS endpoints.
- 02
Return 200 within 10 seconds
PYMSTR times out the webhook after 10 seconds. If your handler is slow, return 200 immediately and process the event asynchronously.
- 03
Store the secret securely
Same as the API key, store the secret securely in the backend. Do not expose.
- 04
Verify before processing
Run the HMAC verification first. Never trust data.paymentId or data.externalId until the signature passes — they're untrusted input otherwise.
- 05
Handle duplicates + ordering
Events may arrive out of order or be duplicated by retries. Dedupe on (data.paymentId, event) and treat your handlers idempotently — the same event can land twice.