Docs/API/Webhooks

Webhooks Integration

Receive real-time notifications when events happen in EC-Permit. Webhooks push data to your server instead of requiring you to poll.

Available Events

  • form.created — A new form was created
  • form.updated — Form data was modified
  • form.submitted — Form was submitted for approval
  • form.approved — Form was approved
  • form.rejected — Form was rejected
  • form.status_changed — Form status changed (any action)
  • form.deleted — Form was deleted
  • user.invited — User was invited to project
  • user.joined — User accepted invitation
  • user.removed — User was removed from project

Webhook Payload

All webhook payloads follow this structure:

{
  "id": "evt_abc123",
  "event": "form.approved",
  "created_at": "2024-01-15T10:30:00Z",
  "data": {
    "form_id": "frm_xyz789",
    "form_number": "HWP-001",
    "form_type_id": "ft_456",
    "project_id": "proj_123",
    "status": "approved",
    "action_by": {
      "id": "usr_111",
      "name": "John Smith",
      "email": "john@example.com"
    },
    "comment": "All safety requirements verified"
  }
}

Signature Verification

Verify webhook signatures to ensure requests come from EC-Permit:

// Headers included with each webhook
X-Webhook-Signature: sha256=abc123...
X-Webhook-Timestamp: 1705315800

// Verification steps:
1. Concatenate: timestamp + "." + raw_body
2. Compute HMAC-SHA256 using your webhook secret
3. Compare with X-Webhook-Signature header

Replay Protection

Reject webhooks with timestamps older than 5 minutes to prevent replay attacks.

Responding to Webhooks

  • Return 200 OK to acknowledge receipt
  • Respond within 30 seconds
  • Non-2xx responses trigger retries
  • Process asynchronously for long operations

Retry Behavior

Failed webhooks are retried with exponential backoff:

  • 1st retry: 1 minute
  • 2nd retry: 5 minutes
  • 3rd retry: 30 minutes
  • 4th retry: 2 hours
  • 5th retry: 24 hours
  • After 5 failures: webhook disabled, email notification sent

Example Handler (Node.js)

app.post('/webhooks/ecpermit', (req, res) => {
  // Verify signature (see above)
  if (!verifySignature(req)) {
    return res.status(401).send('Invalid signature');
  }

  const { event, data } = req.body;

  switch (event) {
    case 'form.approved':
      handleFormApproved(data);
      break;
    case 'form.rejected':
      handleFormRejected(data);
      break;
    // ... handle other events
  }

  res.status(200).send('OK');
});