Skip to main content

Webhooks

Webhooks tell your system when an asynchronous Payscribe operation changes state. Treat each delivery as a secure, at-least-once event: verify it, record it once, acknowledge quickly, and process slow work asynchronously.

Delivery flow

StepWhat happensYour responsibility
1. DeliverPayscribe sends an HTTPS POST.Receive the raw request body.
2. VerifyThe delivery is signed.Check timestamp and HMAC signature.
3. DeduplicateA delivery can be retried.Store X-Payscribe-Event-Id with a unique constraint.
4. ProcessThe event requires business work.Return 200 quickly; queue reconciliation and fulfilment.

Configure an endpoint

  1. In the dashboard, open Settings → Webhooks.
  2. Register a publicly reachable HTTPS URL that accepts POST requests.
  3. Select the events your integration needs.
  4. Store the webhook secret in your server environment, separately from the API key.

Return 200 OK within five seconds. Use a temporary HTTPS tunnel only for sandbox development; production needs a stable production endpoint.

Verify every delivery

HeaderPurpose
X-Payscribe-Signaturev1= HMAC-SHA256 signature
X-Payscribe-Event-IdUnique delivery ID for deduplication
X-Payscribe-TimestampUnix timestamp; reject stale events

The signed value is timestamp + "." + event_id + "." + raw_body.

import crypto from 'node:crypto';
import express from 'express';

const app = express();

// Register before express.json(); req.body must remain a Buffer.
app.post('/webhooks/payscribe', express.raw({type: 'application/json'}), async (req, res) => {
const timestamp = req.headers['x-payscribe-timestamp'];
const eventId = req.headers['x-payscribe-event-id'];
const signature = req.headers['x-payscribe-signature'];
if (!signature || !timestamp || !eventId) return res.sendStatus(401);
const expected = `v1=${crypto.createHmac('sha256', process.env.PAYSCRIBE_WEBHOOK_SECRET)
.update(`${timestamp}.${eventId}.${req.body.toString('utf8')}`).digest('hex')}`;
const received = Buffer.from(signature);
const calculated = Buffer.from(expected);
if (received.length !== calculated.length || !crypto.timingSafeEqual(calculated, received)) return res.sendStatus(401);
// Insert eventId with a unique DB constraint, queue work, then acknowledge.
return res.sendStatus(200);
});

Test and recover

In sandbox, trigger an event with Simulate transfer. Verify the signature, record the event ID, and reconcile final state with the API or dashboard. Test duplicate deliveries and invalid signatures before production.

Never assume delivery order across event types or fulfil from an unverified payload. For payloads, retry behaviour, and the event catalogue, use the Webhooks & Events reference.

Was this page helpful?

Report a docs issue →