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

  1. Stand up an HTTPS endpoint on your side that accepts POST requests and returns 2xx quickly.
  2. Send your endpoint URL to your Pexx contact for both Sandbox and Production. 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.
  3. On every incoming request, verify the signature (see §4) and process the event idempotently by X-Webhook-Event-Id.
  4. Reply with 2xx within 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

RequirementDetail
ProtocolHTTPS only. Plain http:// URLs are not accepted.
MethodPOST
Response timeReply with a 2xx status within 15 seconds (otherwise the call is treated as failed).
IdempotencyThe same event ID may be delivered more than once. Multiple processings must be safe.
Signature verificationRequired 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

HeaderDescription
Content-TypeAlways application/json.
X-Webhook-Event-IdUnique event ID (UUID). Use this for idempotent processing.
X-Webhook-Event-TypeEvent type, e.g. transaction.updated.
X-Webhook-TimestampUnix timestamp (in milliseconds) when Pexx initiated this request. Used for signature and replay protection.
X-Webhook-Signaturesha256=<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.
  }
}
FieldTypeDescription
idstringThe event ID. Same value as the X-Webhook-Event-Id header.
groupstringEvent group, lowercase (e.g. transaction).
typestringEvent type (e.g. transaction.updated).
occurred_at_msnumberWhen the business event happened (UTC, milliseconds since epoch).
dataobjectEvent-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 hex

Compare 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-Timestamp differs from your server time by more than a small window (we recommend 5 minutes).
  • Track X-Webhook-Event-Id in 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:

AttemptWait before next retry
1 → 230 s
2 → 360 s
3 → 42 min
4 → 54 min
5 → 68 min
6 → 716 min
7 → 832 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_ms and 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 2xx immediately; do the heavy work asynchronously.
  • Don't trust ordering. Use occurred_at_ms and 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 3xx redirects or respond to 401/403 challenges.

7. Integration Checklist

  • Stand up an HTTPS endpoint that returns 2xx within 15 seconds.
  • Send your Sandbox endpoint URL to Pexx and securely store the whsec_... secret you receive back.
  • Verify X-Webhook-Signature and X-Webhook-Timestamp on every request.
  • Deduplicate by X-Webhook-Event-Id to 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.