Webhooks Guide
Webhooks allow you to receive real-time notifications when events occur in the SBM CRM Platform. This guide explains how to set up and use webhooks.
What are Webhooks?
Webhooks are HTTP callbacks that notify your application when specific events occur. Instead of polling the API, you receive instant notifications.
Supported Events
Customer Events
customer.created- New customer registeredcustomer.updated- Customer profile updatedcustomer.tier_changed- Customer tier upgraded/downgradedcustomer.deleted- Customer account deleted
Loyalty Events
points.earned- Customer earned pointspoints.redeemed- Customer redeemed pointspoints.expired- Points expiredreward.claimed- Reward claimed by customerreward.redeemed- Reward redeemed at store
Campaign Events
campaign.created- New campaign createdcampaign.started- Campaign startedcampaign.ended- Campaign endedcampaign.participant.joined- Customer joined campaigncampaign.participant.completed- Customer completed campaign
Transaction Events
transaction.completed- Purchase transaction completedtransaction.refunded- Transaction refunded
Notification Events
notification.sent- Notification sent to customernotification.delivered- Notification deliverednotification.failed- Notification delivery failed
Setting Up Webhooks
Step 1: Create Webhook Endpoint
Create an HTTP endpoint in your application that can receive POST requests:
// Express.js example
app.post('/webhooks/sbmcrm', async (req, res) => {
const event = req.body;
// Verify webhook signature
const isValid = verifyWebhookSignature(req);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the event
await handleWebhookEvent(event);
// Respond quickly
res.status(200).send('OK');
});
Step 2: Register Webhook URL
Register your webhook endpoint in the Admin Web App:
- Navigate to Settings → Webhooks
- Click Create Webhook
- Enter your webhook URL
- Select events to subscribe to
- Save the webhook
Or use the API:
POST /api/v1/webhooks
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"url": "https://your-app.com/webhooks/sbmcrm",
"events": [
"customer.created",
"points.earned",
"campaign.participant.joined"
],
"secret": "your_webhook_secret"
}
Webhook Payload
Event Structure
{
"id": "evt_1234567890",
"type": "customer.created",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"customer": {
"id": "cus_12345",
"name": "John Doe",
"email": "john@example.com",
"tier": "bronze"
}
}
}
Example: Customer Created
{
"id": "evt_abc123",
"type": "customer.created",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"customer": {
"id": "cus_12345",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"tier": "bronze",
"points_balance": 0,
"created_at": "2024-01-15T10:30:00Z"
}
}
}
Example: Points Earned
{
"id": "evt_def456",
"type": "points.earned",
"created_at": "2024-01-15T11:00:00Z",
"data": {
"customer_id": "cus_12345",
"points": 100,
"reason": "Purchase bonus",
"transaction_id": "txn_789",
"new_balance": 100
}
}
Example: Campaign Participant Joined
{
"id": "evt_ghi789",
"type": "campaign.participant.joined",
"created_at": "2024-01-15T12:00:00Z",
"data": {
"campaign_id": "camp_456",
"campaign_name": "Summer Sale 2024",
"customer_id": "cus_12345",
"joined_at": "2024-01-15T12:00:00Z"
}
}
Webhook Security
Signature Verification
All webhooks include a signature header for verification:
X-SBMCRM-Signature: t=1609459200,v1=abc123def456...
Verifying Signatures
const crypto = require('crypto');
function verifyWebhookSignature(req, secret) {
const signature = req.headers['x-sbmcrm-signature'];
if (!signature) return false;
const [timestamp, ...signatures] = signature.split(',');
const timestampValue = timestamp.split('=')[1];
// Check timestamp (prevent replay attacks)
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - parseInt(timestampValue)) > 300) {
return false; // More than 5 minutes old
}
// Verify signature
const payload = JSON.stringify(req.body);
const signedPayload = `${timestampValue}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
return signatures.some(sig => {
const sigValue = sig.split('=')[1];
return crypto.timingSafeEqual(
Buffer.from(sigValue),
Buffer.from(expectedSignature)
);
});
}
Python Example
import hmac
import hashlib
import time
def verify_webhook_signature(request, secret):
signature = request.headers.get('X-SBMCRM-Signature')
if not signature:
return False
# Parse signature
parts = signature.split(',')
timestamp = None
signatures = []
for part in parts:
if part.startswith('t='):
timestamp = int(part.split('=')[1])
elif part.startswith('v1='):
signatures.append(part.split('=')[1])
# Check timestamp
current_time = int(time.time())
if abs(current_time - timestamp) > 300:
return False
# Verify signature
payload = request.get_data()
signed_payload = f"{timestamp}.{payload.decode()}"
expected_signature = hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
return any(
hmac.compare_digest(sig, expected_signature)
for sig in signatures
)
Handling Webhooks
Idempotency
Webhooks may be delivered multiple times. Always make your handlers idempotent:
async function handleCustomerCreated(event) {
const customerId = event.data.customer.id;
// Check if already processed
const existing = await db.findWebhookEvent(event.id);
if (existing) {
return; // Already processed
}
// Process event
await processCustomer(customerId);
// Mark as processed
await db.saveWebhookEvent(event.id, {
processed_at: new Date(),
event_type: event.type
});
}
Error Handling
Always respond quickly, even if processing fails:
app.post('/webhooks/sbmcrm', async (req, res) => {
try {
// Verify signature
if (!verifyWebhookSignature(req, webhookSecret)) {
return res.status(401).send('Invalid signature');
}
// Queue for async processing
await queueWebhookEvent(req.body);
// Respond immediately
res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
res.status(500).send('Internal error');
}
});
Retry Logic
The platform will retry failed webhook deliveries:
- Initial attempt: Immediate
- 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 and requires manual intervention.
Testing Webhooks
Using Webhook Testing Tools
Use tools like ngrok or webhook.site for local testing:
# Start ngrok tunnel
ngrok http 3000
# Use the ngrok URL as your webhook URL
# https://abc123.ngrok.io/webhooks/sbmcrm
Manual Testing
Trigger test events from the Admin Web App:
- Navigate to Settings → Webhooks
- Select your webhook
- Click Send Test Event
- Choose an event type
- Verify the webhook is received
Webhook Management API
List Webhooks
GET /api/v1/webhooks
Authorization: Bearer YOUR_API_KEY
Get Webhook
GET /api/v1/webhooks/whk_12345
Authorization: Bearer YOUR_API_KEY
Update Webhook
PATCH /api/v1/webhooks/whk_12345
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"events": [
"customer.created",
"points.earned"
],
"active": true
}
Delete Webhook
DELETE /api/v1/webhooks/whk_12345
Authorization: Bearer YOUR_API_KEY
Best Practices
- Respond quickly - Return 200 OK within 5 seconds
- Verify signatures - Always verify webhook signatures
- Handle idempotency - Process events idempotently
- Queue for processing - Don't do heavy processing in the webhook handler
- Log everything - Log all webhook events for debugging
- Monitor failures - Set up alerts for webhook delivery failures
- Use HTTPS - Always use HTTPS for webhook endpoints
- Test thoroughly - Test webhook handling in staging before production
Troubleshooting
Webhook Not Received
- Check webhook URL is accessible (not behind firewall)
- Verify webhook is active in Admin Web App
- Check webhook logs for delivery attempts
- Verify SSL certificate is valid
Signature Verification Fails
- Ensure you're using the correct webhook secret
- Check timestamp is within 5 minutes
- Verify payload is not modified before verification
- Ensure you're using the raw request body
Duplicate Events
- Implement idempotency checks using event ID
- Check if webhook is registered multiple times
- Verify your handler is not triggering additional events