Webhooks
Integrate with external services by receiving real-time notifications when events occur
Introduction to Webhooks
Webhooks allow you to receive real-time HTTP notifications when events occur in your organization. Instead of polling for changes, your application can be notified immediately when something happens, enabling seamless integrations with external services.
How Webhooks Work
The Flow
- You register a webhook URL with the platform
- You select which events should trigger the webhook
- When a selected event occurs, we send an HTTP POST request to your URL
- Your server processes the event and responds with a 2xx status code
Webhook Payload
Each webhook delivers a JSON payload containing:
{
"id": "evt_abc123",
"type": "experiment.status_changed",
"timestamp": "2024-01-15T10:30:00Z",
"organizationId": "org_xyz789",
"data": {
// Event-specific data
}
}Setting Up Webhooks
Creating a Webhook
To create a new webhook:
- Navigate to Settings > Webhooks
- Click Add Webhook
- Configure the webhook:
- URL - The endpoint that will receive webhook events
- Triggers - Select which events should trigger this webhook
- Secret - An optional secret for verifying webhook signatures
- Click Create
Webhook Properties
- URL - Must be a valid HTTPS endpoint (HTTP allowed for localhost testing)
- Triggers - Array of event types to subscribe to
- Secret - Used for HMAC signature verification
- Active - Whether the webhook is currently enabled
Available Events
Experiment Events
| Event | Description |
|---|---|
experiment.created | A new experiment was created |
experiment.updated | An experiment was modified |
experiment.deleted | An experiment was deleted |
experiment.status_changed | An experiment changed status (draft, active, winner, loser) |
experiment.started | An experiment started running |
experiment.completed | An experiment finished |
OKR Events
| Event | Description |
|---|---|
objective.created | A new objective was created |
objective.updated | An objective was modified |
objective.deleted | An objective was deleted |
objective.status_changed | An objective changed status |
key_result.created | A new key result was added |
key_result.updated | A key result was modified |
key_result.progress_updated | Key result progress was updated |
Document Events
| Event | Description |
|---|---|
doc.created | A new document was created |
doc.updated | A document was modified |
doc.deleted | A document was deleted |
doc.published | A document was published |
doc.shared | A document was shared externally |
Team Events
| Event | Description |
|---|---|
member.joined | A new member joined the organization |
member.left | A member left the organization |
member.role_changed | A member's role was changed |
invitation.sent | An invitation was sent |
invitation.accepted | An invitation was accepted |
Workflow Events
| Event | Description |
|---|---|
workflow.executed | A workflow was triggered and executed |
workflow.failed | A workflow execution failed |
Event Payloads
Experiment Status Changed
{
"id": "evt_abc123",
"type": "experiment.status_changed",
"timestamp": "2024-01-15T10:30:00Z",
"organizationId": "org_xyz789",
"data": {
"experimentId": "exp_123",
"name": "Homepage CTA Test",
"previousStatus": "ACTIVE",
"newStatus": "WINNER",
"changedBy": {
"id": "user_456",
"name": "John Doe",
"email": "[email protected]"
}
}
}Key Result Progress Updated
{
"id": "evt_def456",
"type": "key_result.progress_updated",
"timestamp": "2024-01-15T10:30:00Z",
"organizationId": "org_xyz789",
"data": {
"keyResultId": "kr_789",
"title": "Increase conversion rate to 5%",
"objectiveId": "obj_abc",
"previousValue": 3.2,
"newValue": 4.1,
"targetValue": 5.0,
"progress": 82,
"updatedBy": {
"id": "user_456",
"name": "John Doe"
}
}
}Member Joined
{
"id": "evt_ghi789",
"type": "member.joined",
"timestamp": "2024-01-15T10:30:00Z",
"organizationId": "org_xyz789",
"data": {
"memberId": "user_new123",
"name": "Jane Smith",
"email": "[email protected]",
"role": "MEMBER",
"invitedBy": {
"id": "user_456",
"name": "John Doe"
}
}
}Security
Verifying Webhook Signatures
When you provide a secret, each webhook request includes a signature header:
X-Webhook-Signature: sha256=abc123...Verify the signature to ensure the webhook came from us:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return `sha256=${expectedSignature}` === signature;
}
// In your webhook handler
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
// ...
res.status(200).send('OK');
});Best Practices
- Always verify signatures in production
- Use HTTPS for your webhook endpoint
- Respond quickly (within 5 seconds)
- Process asynchronously if handling takes time
- Return 2xx for successful receipt, even if processing happens later
Handling Webhooks
Responding to Webhooks
Your endpoint must respond within 5 seconds with a 2xx status code. If you need more time to process:
- Acknowledge receipt immediately (200 OK)
- Queue the webhook for async processing
- Process in the background
Retry Policy
If your endpoint fails to respond with 2xx, we retry with exponential backoff:
- Retry 1: After 1 minute
- Retry 2: After 5 minutes
- Retry 3: After 30 minutes
- Retry 4: After 2 hours
- Retry 5: After 24 hours
After 5 failed attempts, the webhook is marked as failed.
Idempotency
Webhooks may be delivered more than once. Handle this by:
- Using the
idfield to track processed events - Making your handlers idempotent
- Storing processed event IDs to prevent duplicates
const processedEvents = new Set();
app.post('/webhook', async (req, res) => {
const eventId = req.body.id;
if (processedEvents.has(eventId)) {
return res.status(200).send('Already processed');
}
// Process the event
await handleEvent(req.body);
processedEvents.add(eventId);
res.status(200).send('OK');
});Managing Webhooks
Viewing Webhooks
See all configured webhooks at Settings > Webhooks:
- Webhook URL
- Configured triggers
- Last triggered timestamp
- Delivery status
Editing Webhooks
Update a webhook's configuration:
- Click on the webhook
- Modify URL, triggers, or secret
- Save changes
Deleting Webhooks
Remove a webhook:
- Click on the webhook
- Click Delete
- Confirm deletion
Deleted webhooks stop receiving events immediately.
Testing Webhooks
Test your webhook endpoint:
- Click Test on a webhook
- Select an event type
- We send a test payload to your URL
- View the response
Test events include "test": true in the payload.
Use Cases
Slack Notifications
Send a message to Slack when experiments complete:
- Create a Slack incoming webhook
- Add our webhook with Slack's URL
- Subscribe to
experiment.status_changed - Your team gets notified of wins and losses
Sync to External Systems
Keep external tools updated:
- Sync experiment results to your data warehouse
- Update project management tools when status changes
- Trigger CI/CD pipelines based on events
Custom Automation
Build custom automation:
- Send celebration emails when OKRs hit 100%
- Alert stakeholders when experiments need attention
- Generate reports when periods close
Troubleshooting
Webhook Not Received
- Check your endpoint is accessible from the internet
- Verify HTTPS certificate is valid
- Check firewall rules allow incoming requests
- Review server logs for errors
Signature Verification Failing
- Ensure you're using the exact payload (no modifications)
- Check the secret matches exactly
- Verify you're using SHA256 HMAC
Timeouts
- Respond immediately, process async
- Check your server's response time
- Ensure no blocking operations before responding