Webhook Integration
Receive real-time notifications when data changes in your Paismo organization.
- Register:
curl -X POST .../v1/webhooks - Verify:
curl -X POST .../v1/webhooks/{id}/verify
1. Webhook Lifecycle
Every webhook subscription moves through these states:
- Pending Verification: Initial state. No events are sent until the URL is verified.
- Active: The handshake succeeded. The webhook is receiving events.
- Paused: Manually disabled by you. No events are sent or queued.
- Failed: Automatically disabled after consecutive delivery failures.
2. The Handshake (Ping Challenge)
To prevent malicious use of the system, Nexus API requires an active handshake before a webhook is activated.
- Register your webhook via
POST /v1/webhooks. - Call
POST /v1/webhooks/{id}/verifywith the token provided. - Nexus will immediately send a
POSTrequest to your URL.
The Challenge Payload
// POST https://your-server.com/webhook
// Headers: X-Paismo-Verification: handshake
{
"challenge": "v_tok_random_123..."
}
Your server must respond with 200 OK and the exact same JSON body to complete the verification.
3. Security & Headers
All production webhooks are signed with an HMAC-SHA256 signature using your webhook's signing_secret. You should verify this signature to ensure the data is legitimate.
| Header | Description | Example |
|---|---|---|
Content-Type |
Always JSON | application/json |
X-Paismo-Signature |
HMAC-SHA256 signature | abc123def456... |
X-Paismo-Event-Id |
Unique identifier for the source event | 550e8400-e29b-41d4-a716-446655440000 |
X-Paismo-Verification |
Present only during the handshake phase | handshake |
4. Event Format
All event payloads share a common envelope structure:
{
"id": "uuid",
"event_type": "employee.created",
"event_id": "uuid",
"timestamp": "2026-01-21T10:30:00.000Z",
"data": {
// Event-specific data
}
}
5. Event Catalog
Employee Events
employee.created: Fired when a new employee record is created. Data includesemployee_id,first_name,last_name,work_email,department_id, andstatus.employee.updated: Fired when an employee is modified. Thedataobject includes achanged_fieldsarray and achangesobject mapping fields to theirold_valueandnew_value.employee.terminated: Fired when an employee status changes to terminated or an end date is set. Data includes thetermination_date.
Payroll Events
payroll.period.created: Fired when a new payroll period is established (e.g., January 2026 Bi-Weekly).payroll.created: Fired when a payroll calculation run is initiated.payroll.approved: Fired when a manager approves a calculated payroll (is_approvedbecomes true).payroll.closed: Fired when the payroll is finalized and locked (is_closedbecomes true). Ready for payment processing.
6. Granular Filtering
When registering a webhook, you can define specific filters for update events to prevent noise. Filters are applied to the `event_types` array payload.
Field Filtering: Only trigger if specific fields have changed.
{
"type": "employee.updated",
"filters": {
"fields": ["status", "department_id"]
}
}
Value Filtering: Only trigger if a field equals a specific value (exact string match).
{
"type": "payroll.updated",
"filters": {
"match": {
"field": "status",
"value": "CLOSED"
}
}
}
7. Implementation Example (Node.js/Express)
Here is a complete example of a receptor that handles both the Handshake and Event Delivery.
const express = require('express');
const crypto = require('crypto');
const app = express();
// Important: Read body as raw buffer if using strict timestamp/body signatures,
// or standard JSON if signature is just calculated against the JSON string.
// The Nexus API signs the raw JSON payload body.
app.use(express.json());
const SIGNING_SECRET = process.env.PAISMO_WEBHOOK_SECRET;
app.post('/webhook', (req, res) => {
// A. Handle Active Handshake
if (req.headers['x-paismo-verification'] === 'handshake') {
return res.status(200).json({ challenge: req.body.challenge });
}
// B. Verify Signature for Production Events
const signature = req.headers['x-paismo-signature'];
const expected = crypto
.createHmac('sha256', SIGNING_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
// Perform timing-safe comparison
const isValid = crypto.timingSafeEqual(
Buffer.from(signature || '', 'utf8'),
Buffer.from(expected, 'utf8')
);
if (!isValid) {
return res.status(403).send('Invalid signature');
}
// C. Process the Event
const event = req.body;
const sourceEventId = req.headers['x-paismo-event-id'];
console.log('Received event:', event.event_type, 'with internal ID:', event.id);
if (event.event_type === 'employee.updated') {
console.log('Fields changed:', event.data.changed_fields);
}
res.status(200).send('OK');
});
app.listen(3000);
8. Retention Policy
Nexus API keeps a history of webhook delivery attempts for 90 days. You can view these logs via the /v1/webhooks/{id}/deliveries endpoint to audit failures or manually trigger retries.