View API Spec

Webhook Integration

Receive real-time notifications when data changes in your Paismo organization.

Quick Start: You can set up a webhook in two steps:
  1. Register: curl -X POST .../v1/webhooks
  2. Verify: curl -X POST .../v1/webhooks/{id}/verify

1. Webhook Lifecycle

Every webhook subscription moves through these states:

2. The Handshake (Ping Challenge)

To prevent malicious use of the system, Nexus API requires an active handshake before a webhook is activated.

  1. Register your webhook via POST /v1/webhooks.
  2. Call POST /v1/webhooks/{id}/verify with the token provided.
  3. Nexus will immediately send a POST request 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

Payroll Events

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.