phliusphlius

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

  1. You register a webhook URL with the platform
  2. You select which events should trigger the webhook
  3. When a selected event occurs, we send an HTTP POST request to your URL
  4. 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:

  1. Navigate to Settings > Webhooks
  2. Click Add Webhook
  3. 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
  4. 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

EventDescription
experiment.createdA new experiment was created
experiment.updatedAn experiment was modified
experiment.deletedAn experiment was deleted
experiment.status_changedAn experiment changed status (draft, active, winner, loser)
experiment.startedAn experiment started running
experiment.completedAn experiment finished

OKR Events

EventDescription
objective.createdA new objective was created
objective.updatedAn objective was modified
objective.deletedAn objective was deleted
objective.status_changedAn objective changed status
key_result.createdA new key result was added
key_result.updatedA key result was modified
key_result.progress_updatedKey result progress was updated

Document Events

EventDescription
doc.createdA new document was created
doc.updatedA document was modified
doc.deletedA document was deleted
doc.publishedA document was published
doc.sharedA document was shared externally

Team Events

EventDescription
member.joinedA new member joined the organization
member.leftA member left the organization
member.role_changedA member's role was changed
invitation.sentAn invitation was sent
invitation.acceptedAn invitation was accepted

Workflow Events

EventDescription
workflow.executedA workflow was triggered and executed
workflow.failedA 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:

  1. Acknowledge receipt immediately (200 OK)
  2. Queue the webhook for async processing
  3. 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 id field 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:

  1. Click on the webhook
  2. Modify URL, triggers, or secret
  3. Save changes

Deleting Webhooks

Remove a webhook:

  1. Click on the webhook
  2. Click Delete
  3. Confirm deletion

Deleted webhooks stop receiving events immediately.

Testing Webhooks

Test your webhook endpoint:

  1. Click Test on a webhook
  2. Select an event type
  3. We send a test payload to your URL
  4. View the response

Test events include "test": true in the payload.

Use Cases

Slack Notifications

Send a message to Slack when experiments complete:

  1. Create a Slack incoming webhook
  2. Add our webhook with Slack's URL
  3. Subscribe to experiment.status_changed
  4. 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

  1. Check your endpoint is accessible from the internet
  2. Verify HTTPS certificate is valid
  3. Check firewall rules allow incoming requests
  4. Review server logs for errors

Signature Verification Failing

  1. Ensure you're using the exact payload (no modifications)
  2. Check the secret matches exactly
  3. Verify you're using SHA256 HMAC

Timeouts

  1. Respond immediately, process async
  2. Check your server's response time
  3. Ensure no blocking operations before responding

On this page