Webhooks
Webhooks allow you to receive real-time notifications when events occur in Oproto.
Coming Soon
Webhook functionality is currently in development.
How Webhooks Work
- An event occurs in Oproto (e.g., company created)
- Oproto sends an HTTP POST to your configured endpoint
- Your server verifies the signature and processes the event
- Your server responds with a 2xx status code
Event Payload
{
"id": "evt_abc123def456",
"type": "company.created",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"id": "comp_xyz789",
"name": "Acme Corporation",
"createdAt": "2024-01-15T10:30:00Z"
}
}
| Field | Description |
|---|---|
id | Unique event identifier |
type | Event type (e.g., company.created) |
timestamp | When the event occurred (ISO 8601) |
data | Event-specific payload |
Event Types
Company Events
| Event | Description |
|---|---|
company.created | A new company was created |
company.updated | A company was modified |
company.deleted | A company was deleted |
Contact Events
| Event | Description |
|---|---|
contact.created | A new contact was created |
contact.updated | A contact was modified |
contact.deleted | A contact was deleted |
Location Events
| Event | Description |
|---|---|
location.created | A new location was created |
location.updated | A location was modified |
location.deleted | A location was deleted |
Verifying Signatures
All webhook requests include a signature header for verification:
X-Oproto-Signature: sha256=5d41402abc4b2a76b9719d911017c592
X-Oproto-Timestamp: 1705315800
Verification Process
import crypto from 'crypto';
function verifyWebhookSignature(payload, signature, timestamp, secret) {
// Prevent replay attacks - reject old timestamps
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - timestamp) > 300) {
throw new Error('Timestamp too old');
}
// Compute expected signature
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Compare signatures (timing-safe)
const expected = Buffer.from(`sha256=${expectedSignature}`);
const received = Buffer.from(signature);
if (!crypto.timingSafeEqual(expected, received)) {
throw new Error('Invalid signature');
}
return true;
}
Handling Webhooks
Express.js Example
import express from 'express';
const app = express();
app.post('/webhooks/oproto',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-oproto-signature'];
const timestamp = req.headers['x-oproto-timestamp'];
try {
verifyWebhookSignature(
req.body.toString(),
signature,
parseInt(timestamp),
process.env.WEBHOOK_SECRET
);
} catch (err) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
switch (event.type) {
case 'company.created':
handleCompanyCreated(event.data);
break;
case 'company.updated':
handleCompanyUpdated(event.data);
break;
// Handle other events...
}
res.status(200).send('OK');
}
);
Retry Policy
If your endpoint doesn't respond with a 2xx status code, Oproto will retry:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failed attempts, the webhook is marked as failed and won't be retried.
Best Practices
Respond Quickly
Return a 200 response immediately, then process the event asynchronously:
app.post('/webhooks/oproto', (req, res) => {
// Acknowledge receipt immediately
res.status(200).send('OK');
// Process asynchronously
processWebhookAsync(req.body).catch(console.error);
});
Handle Duplicates
Webhooks may be delivered more than once. Use the event id for idempotency:
async function processWebhook(event) {
// Check if already processed
if (await isEventProcessed(event.id)) {
return;
}
// Process the event
await handleEvent(event);
// Mark as processed
await markEventProcessed(event.id);
}
Use a Queue
For high-volume webhooks, use a message queue:
app.post('/webhooks/oproto', async (req, res) => {
await messageQueue.publish('webhooks', req.body);
res.status(200).send('OK');
});
Testing Webhooks
Use tools like ngrok to expose your local server:
ngrok http 3000
Then configure the ngrok URL as your webhook endpoint for testing.