Quickstart: Webhooks for Developers¶
This guide walks developers through setting up webhook integration with WhiteLabelCRO.
Prerequisites¶
Before you begin: - Basic understanding of HTTP webhooks and HMAC signatures - Publicly accessible HTTPS endpoint (or ngrok for testing) - Admin access to WhiteLabelCRO CRM - HTTP client or tool (curl, Postman, or code)
Webhooks vs Integration API Actions (Important)¶
Webhooks are outbound only - they deliver events from WhiteLabelCRO to your external system.
To create or update data in WhiteLabelCRO (e.g., create leads, add notes, update client status), use Integration API actions. These are standard HTTP POST/PATCH requests authenticated with an API key.
Integration API actions can be called from: - Zapier (using WhiteLabelCRO actions) - n8n (using HTTP Request nodes) - Make (using HTTP modules) - Custom code or internal systems (direct API calls)
This guide covers receiving events via webhooks only. For writing data into WhiteLabelCRO, see: - Integration Capabilities Overview - Zapier Actions, n8n Actions, or Make Actions
Step 1: Create Your Endpoint¶
Your webhook endpoint must: - Accept HTTP POST requests - Return 2xx status code (200-299) for successful receipt - Respond within 10 seconds - Handle duplicate deliveries (use event ID for deduplication)
Minimum viable endpoint:
POST https://your-domain.com/webhooks/wlcro
Responds with 200 OK and logs the request body.
Step 2: Obtain API Credentials¶
Get API Key¶
- Log in to WhiteLabelCRO CRM
- Navigate to Settings > Integrations > API Keys
- Click Create New Key
- Name it "Webhook Integration"
- Select scope:
webhooks:write - Copy the API key
Note Your Webhook URL¶
Your webhook URL must: - Use HTTPS (HTTP only allowed in development) - Be publicly accessible - Respond to POST requests
For local testing, use ngrok:
ngrok http 3000
Copy the HTTPS URL (e.g., https://abc123.ngrok.io/webhooks/wlcro).
Step 3: Register Your Webhook Subscription¶
Use curl or Postman to create a subscription:
curl -X POST https://your-api-url.com/api/v1/webhooks/subscriptions \
-H "X-Api-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-domain.com/webhooks/wlcro",
"eventTypes": ["client.created", "payment.succeeded"],
"enabled": true
}'
Response:
{
"id": 123,
"url": "https://your-domain.com/webhooks/wlcro",
"eventTypes": ["client.created", "payment.succeeded"],
"signingSecret": "abc123...",
"enabled": true
}
Save the signingSecret - you'll need it for signature verification. It's only shown once.
See Webhooks API documentation for complete details.
Step 4: Implement Signature Verification¶
Every webhook delivery includes:
- X-Webhook-Timestamp header (Unix epoch seconds)
- X-Webhook-Signature header (sha256=<hex>)
Verification steps:
- Extract both headers
- Compute HMAC-SHA256 of
"{timestamp}.{raw_body}"using signing secret - Convert to lowercase hex and prepend
sha256= - Compare with received signature (constant-time comparison)
- Optionally reject old timestamps (> 5 minutes)
Example (pseudocode):
timestamp = request.headers['X-Webhook-Timestamp']
signature = request.headers['X-Webhook-Signature']
payload = timestamp + '.' + raw_request_body
computed = 'sha256=' + hmac_sha256(signing_secret, payload).hex().lower()
if !constant_time_equals(computed, signature):
return 401 Unauthorized
See Webhook Security documentation for code examples.
Step 5: Handle Webhook Delivery¶
Your endpoint should:
1. Verify the signature (Step 4)
2. Parse the JSON body:
{
"id": 12345,
"type": "client.created",
"createdUtc": "2026-01-28T10:30:00Z",
"payload": {
"client_id": 5678,
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com"
}
}
3. Check for duplicates (use id field)
4. Process the event (business logic)
5. Return 200 OK immediately
Don't perform long-running operations before returning. Use queues/background jobs for async processing.
Step 6: Test Your Integration¶
Send a Test Delivery¶
Using the subscription ID from Step 3:
curl -X POST https://your-api-url.com/api/v1/webhooks/subscriptions/123/test \
-H "X-Api-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"eventType": "client.created",
"useSamplePayload": true
}'
Response:
{
"success": true,
"statusCode": 200,
"elapsedMs": 145,
"responseBody": "OK"
}
Success criteria: - Your endpoint receives the POST request - Signature verification passes - Your endpoint returns 200 - Event is logged/processed
Trigger a Real Event¶
Create a test client in WhiteLabelCRO:
- Navigate to Clients > Add New Client
- Fill in details and save
- Within seconds, your webhook should receive
client.createdevent
Check your logs to verify delivery.
Minimal Code Examples¶
Node.js Express¶
const express = require('express');
const crypto = require('crypto');
app.post('/webhooks/wlcro', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.headers['x-webhook-timestamp'];
const signature = req.headers['x-webhook-signature'];
const payload = `${timestamp}.${req.body}`;
const computed = 'sha256=' + crypto
.createHmac('sha256', signingSecret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(signature))) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Event received:', event.type, event.id);
// Process event here
res.status(200).send('OK');
});
Python Flask¶
import hmac
import hashlib
from flask import Flask, request
@app.route('/webhooks/wlcro', methods=['POST'])
def webhook():
timestamp = request.headers.get('X-Webhook-Timestamp')
signature = request.headers.get('X-Webhook-Signature')
payload = f"{timestamp}.{request.data.decode()}"
computed = 'sha256=' + hmac.new(
signing_secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(computed, signature):
return 'Invalid signature', 401
event = request.json
print(f"Event received: {event['type']} {event['id']}")
# Process event here
return 'OK', 200
Error Handling¶
Retry Behavior¶
If your endpoint fails: - Returns non-2xx status - Times out (> 10 seconds) - Connection error
WhiteLabelCRO will retry up to 10 times with exponential backoff.
Circuit Breaker¶
After 20 consecutive failures, the subscription auto-disables. Re-enable after fixing the issue.
Duplicate Deliveries¶
Due to retries, the same event may arrive multiple times. Always check event id for deduplication:
if (processedEventIds.has(event.id)) {
return res.status(200).send('Already processed');
}
processedEventIds.add(event.id);
Going to Production¶
Security Checklist¶
- ✅ Always verify signatures
- ✅ Use HTTPS (not HTTP)
- ✅ Validate timestamp (reject old requests)
- ✅ Don't expose signing secret in logs
- ✅ Use constant-time comparison for signatures
Performance Checklist¶
- ✅ Respond within 10 seconds
- ✅ Use async processing for long operations
- ✅ Implement deduplication
- ✅ Log deliveries for debugging
- ✅ Monitor failure rates
Monitoring¶
Track: - Webhook delivery success rate - Processing time - Duplicate event count - Signature verification failures
Troubleshooting¶
Signature Verification Fails¶
- Using correct signing secret from subscription creation?
- Computing HMAC over
"{timestamp}.{raw_body}"(exact format)? - Using raw request body (before parsing JSON)?
- Using constant-time comparison?
Webhooks Not Arriving¶
- Subscription is enabled?
- URL is publicly accessible?
- Firewall allows WhiteLabelCRO IPs?
- Check delivery logs in WhiteLabelCRO Admin Portal
Timeouts¶
- Endpoint responds within 10 seconds?
- Move long operations to background queue
- Return 200 immediately after validation
Next Steps¶
Add More Event Types¶
Edit your subscription to include:
- payment.failed - Alert on declined payments
- invoice.created - Sync with accounting
- agreement.signed - Trigger post-signature workflows
Implement Business Logic¶
- Update external CRM
- Send notifications
- Trigger workflows
- Sync to data warehouse
Learn More¶
- Event Catalog - All event types and fields
- Webhooks API - Complete API reference
- Webhook Security - Detailed security guide