Skip to main content

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

  1. An event occurs in Oproto (e.g., company created)
  2. Oproto sends an HTTP POST to your configured endpoint
  3. Your server verifies the signature and processes the event
  4. 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"
}
}
FieldDescription
idUnique event identifier
typeEvent type (e.g., company.created)
timestampWhen the event occurred (ISO 8601)
dataEvent-specific payload

Event Types

Company Events

EventDescription
company.createdA new company was created
company.updatedA company was modified
company.deletedA company was deleted

Contact Events

EventDescription
contact.createdA new contact was created
contact.updatedA contact was modified
contact.deletedA contact was deleted

Location Events

EventDescription
location.createdA new location was created
location.updatedA location was modified
location.deletedA 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:

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 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.