Overview
Webhook Overview
PEXX uses webhooks to notify your server about asynchronous events — for example, when a transaction's status changes. Instead of polling our APIs, you provide an HTTPS endpoint and Pexx delivers a signed JSON payload to it as soon as the event happens.
This page covers everything you need to integrate: how your endpoint is set up, the request format, how to verify signatures, and how to handle retries.
1. Quick Start
- Stand up an HTTPS endpoint on your side that accepts
POSTrequests and returns2xxquickly. - Send your endpoint URL to your Pexx contact for both
SandboxandProduction. The URL and the signing secret (whsec_...) are configured manually by Pexx and shared back to you over a secure channel — store the secret securely. - On every incoming request, verify the signature (see §4) and process the event idempotently by
X-Webhook-Event-Id. - Reply with
2xxwithin 15 seconds.
Per-event-type subscription (choosing which event types your endpoint receives) is still under development. For now, your endpoint will receive every event type PEXX emits for your merchant.
2. Endpoint Requirements
| Requirement | Detail |
|---|---|
| Protocol | HTTPS only. Plain http:// URLs are not accepted. |
| Method | POST |
| Response time | Reply with a 2xx status within 15 seconds (otherwise the call is treated as failed). |
| Idempotency | The same event ID may be delivered more than once. Multiple processings must be safe. |
| Signature verification | Required for production traffic. See §4. |
Tip: do not perform long-running work inside the webhook handler. Persist or enqueue the event first, return
2xx, and process it asynchronously.
3. Request Format
Pexx delivers events as an HTTP POST with a JSON body.
3.1 Headers
| Header | Description |
|---|---|
Content-Type | Always application/json. |
X-Webhook-Event-Id | Unique event ID (UUID). Use this for idempotent processing. |
X-Webhook-Event-Type | Event type, e.g. transaction.updated. |
X-Webhook-Timestamp | Unix timestamp (in milliseconds) when Pexx initiated this request. Used for signature and replay protection. |
X-Webhook-Signature | sha256=<hex> — HMAC-SHA256 signature of the request (see §4). |
3.2 Body
{
"id": "9c4f8a72-3e71-4f4a-bc2a-1f0d8b8e1a91",
"group": "transaction",
"type": "transaction.updated",
"occurred_at_ms": 1745793600000,
"data": {
// Event payload — schema depends on the event type.
// See the per-event reference for details.
}
}| Field | Type | Description |
|---|---|---|
id | string | The event ID. Same value as the X-Webhook-Event-Id header. |
group | string | Event group, lowercase (e.g. transaction). |
type | string | Event type (e.g. transaction.updated). |
occurred_at_ms | number | When the business event happened (UTC, milliseconds since epoch). |
data | object | Event-specific payload. See the per-event reference for the exact schema. |
4. Signature Verification
To protect your endpoint from spoofed traffic, every request is signed with HMAC-SHA256 using the secret Pexx shared with you. Verify the signature on every incoming request.
4.1 Algorithm
signed_payload = X-Webhook-Timestamp + "." + <raw request body>
expected_sig = HMAC_SHA256(secret, signed_payload) // lowercase hexCompare expected_sig against the value after sha256= in X-Webhook-Signature using a constant-time comparison. If they match, the request is authentic.
4.2 Replay Protection
- Reject any request whose
X-Webhook-Timestampdiffers from your server time by more than a small window (we recommend 5 minutes). - Track
X-Webhook-Event-Idin your own datastore and skip duplicates.
4.3 Verification Example (Node.js)
const crypto = require('crypto');
function verifyPexxWebhook(rawBody, headers, secret) {
const timestamp = headers['x-webhook-timestamp'];
const signature = (headers['x-webhook-signature'] || '').replace(/^sha256=/, '');
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(signature, 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Verification must use the raw, unmodified request body as it was received over the wire. Any re-serialization or reformatting will produce a different signature.
5. Retries
If your endpoint does not return 2xx within 15 seconds (or returns a non-2xx status, or the network call fails), Pexx will automatically retry with exponential backoff:
| Attempt | Wait before next retry |
|---|---|
| 1 → 2 | 30 s |
| 2 → 3 | 60 s |
| 3 → 4 | 2 min |
| 4 → 5 | 4 min |
| 5 → 6 | 8 min |
| 6 → 7 | 16 min |
| 7 → 8 | 32 min |
- An event will be retried up to 8 times in total. After that it is permanently marked as failed and will not be retried.
- Successful delivery on any attempt stops the retries.
Ordering is not guaranteed. Retries and backoff may cause an older event to arrive after a newer one. Use
occurred_at_msand the state fields inside the event payload to determine the correct order on your side.
6. Best Practices
- Always verify the signature. Treat any request that fails verification as untrusted and respond with
401. - Be idempotent. Deduplicate by
X-Webhook-Event-Id; the same event may legitimately arrive more than once. - Respond fast. Acknowledge with
2xximmediately; do the heavy work asynchronously. - Don't trust ordering. Use
occurred_at_msand resource state fields, not arrival order. - Keep your secret safe. Store it in your secrets manager, never commit it to source control. Contact Pexx to rotate it if you suspect it has leaked.
- Handle redirects/auth challenges yourself. Pexx will not follow
3xxredirects or respond to401/403challenges.
7. Integration Checklist
- Stand up an HTTPS endpoint that returns
2xxwithin 15 seconds. - Send your Sandbox endpoint URL to Pexx and securely store the
whsec_...secret you receive back. - Verify
X-Webhook-SignatureandX-Webhook-Timestampon every request. - Deduplicate by
X-Webhook-Event-Idto ensure idempotency. - Trigger a test transaction in Sandbox and confirm your endpoint receives and processes the event.
- Repeat the registration in Production and validate again.
Updated about 1 hour ago