Webhook Integration Summary
✅ Implemented Webhook Triggers
All webhook events are now fully integrated into your backend system. Here's what triggers each webhook:
1. Campaign Events (Already Working)
campaign.started
- Triggered when: Campaign status changes from
DRAFT→ACTIVE - Payload:
{
"campaignId": 123,
"campaignName": "Summer Sale Campaign",
"startDate": "2025-11-23T10:00:00.000Z",
"endDate": "2025-12-23T10:00:00.000Z"
}
campaign.paused
- Triggered when: Campaign status changes to
PAUSED - Payload:
{
"campaignId": 123,
"campaignName": "Summer Sale Campaign",
"pausedAt": "2025-11-23T10:00:00.000Z"
}
campaign.completed
- Triggered when: Campaign status changes to
COMPLETED - Payload:
{
"campaignId": 123,
"campaignName": "Summer Sale Campaign",
"completedAt": "2025-11-23T10:00:00.000Z",
"analytics": {
"totalImpressions": 15000,
"totalConversions": 450,
"conversionRate": 3.0,
"bestVariant": "variant_a",
"statisticalSignificance": true,
"winningVariant": "variant_a"
}
}
campaign.winner_declared
- Triggered when: Campaign completes with statistically significant winner
- Payload:
{
"campaignId": 123,
"campaignName": "Summer Sale Campaign",
"winner": "variant_a",
"pValue": 0.02,
"confidenceLevel": 95,
"effectSize": 0.15,
"interpretation": "Significant improvement detected"
}
2. Content Events (✨ Newly Implemented)
content.published
- Triggered when: Content view status changes to
PUBLISHED - Payload:
{
"contentViewId": 456,
"contentViewName": "Homepage Banner",
"publishedAt": "2025-11-23T10:00:00.000Z",
"hasItems": true
}
content.unpublished
- Triggered when: Content view status changes to
ARCHIVED(fromPUBLISHED) - Payload:
{
"contentViewId": 456,
"contentViewName": "Homepage Banner",
"unpublishedAt": "2025-11-23T10:00:00.000Z",
"previousStatus": "published"
}
3. Form Events (✨ Newly Implemented)
form.submitted
- Triggered when: User submits a form through the SDK
- Endpoint:
POST /apps-external/:appId/submit-formorPOST /content/submit-form - Payload:
{
"formSubmissionId": 789,
"contentViewId": 456,
"contentViewName": "Contact Form",
"submittedAt": "2025-11-23T10:00:00.000Z",
"data": {
"name": "John Doe",
"email": "john@example.com",
"message": "Hello!"
},
"userId": "user_123",
"sessionId": "session_abc",
"environment": "production"
}
4. Milestone Events (Not Yet Implemented)
milestone.reached
- Status: Placeholder - needs implementation
- Suggested Use Cases:
- 1,000 campaign impressions reached
- 100 form submissions received
- 10,000 users milestone
- Custom analytics thresholds
🧪 Testing Your Webhooks
Prerequisites
- Create a webhook in your admin panel
- Set up a webhook receiver endpoint (or use a service like webhook.site)
- Subscribe to the events you want to test
Test Scenarios
Test 1: Campaign Started
# 1. Create a campaign in DRAFT status via your admin panel
# 2. Update campaign status to ACTIVE
PATCH /api/v1/campaigns/:campaignId/status
{
"status": "active"
}
# Expected: webhook.campaign_started should be triggered
Test 2: Content Published
# 1. Create content view with unpublished changes
# 2. Publish the content
PATCH /api/v1/content/:appId/:contentViewId
{
"status": "published"
}
# Expected: webhook.content_published should be triggered
Test 3: Form Submitted
# 1. Configure a form in your content view
# 2. Submit the form via SDK or API
POST /api/v1/apps-external/:appId/submit-form
{
"contentViewId": 123,
"data": {
"name": "Test User",
"email": "test@example.com"
},
"userId": "test_user_123",
"sessionId": "test_session_abc",
"environment": "production"
}
# Expected: webhook.form_submitted should be triggered
🔐 Webhook Security
HMAC Signature Verification
All webhook payloads are signed with HMAC-SHA256 if you provide a secret key.
Verification Example (Node.js):
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(payload.data)); // Only the 'data' field is signed
const expectedSignature = `sha256=${hmac.digest('hex')}`;
return signature === expectedSignature;
}
// In your webhook endpoint
app.post('/webhook', (req, res) => {
const signature = req.headers['x-resync-signature'];
const payload = req.body;
if (verifyWebhookSignature(payload, signature, 'your-secret-key')) {
// Valid webhook
console.log('Event:', payload.event);
console.log('Data:', payload.data);
res.sendStatus(200);
} else {
// Invalid signature
res.sendStatus(401);
}
});
Verification Example (Python):
import hmac
import hashlib
import json
def verify_webhook_signature(payload_data, signature, secret):
data_json = json.dumps(payload_data)
expected_signature = 'sha256=' + hmac.new(
secret.encode(),
data_json.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
# In your Flask/FastAPI webhook endpoint
@app.post('/webhook')
def handle_webhook(request):
signature = request.headers.get('X-Resync-Signature')
payload = request.json
if verify_webhook_signature(payload['data'], signature, 'your-secret-key'):
# Valid webhook
print(f"Event: {payload['event']}")
print(f"Data: {payload['data']}")
return {'status': 'ok'}, 200
else:
return {'error': 'Invalid signature'}, 401
📊 Webhook Headers
Every webhook delivery includes these headers:
Content-Type: application/json
User-Agent: Resync-Webhooks/1.0
X-Resync-Event: campaign.started
X-Resync-Delivery-ID: 12345
X-Resync-Signature: sha256=abc123... (if secret configured)
[...custom headers you configured]
🔄 Retry Logic
- Max Retries: Configurable per webhook (default: 3)
- Retry Schedule: Exponential backoff
- 1st retry: 2 minutes after failure
- 2nd retry: 4 minutes after failure
- 3rd retry: 8 minutes after failure
- Auto-Disable: Webhooks are marked as
FAILEDafter 10+ consecutive failures with 0 successes
📝 Webhook Payload Format
All webhooks follow this structure:
{
"event": "campaign.started",
"timestamp": "2025-11-23T10:00:00.000Z",
"data": {
// Event-specific payload (see above for each event type)
}
}
🎯 Next Steps
Recommended Enhancements
- Implement
milestone.reached: Add logic to track and trigger milestones - Add Webhook Logging: Log all webhook deliveries for debugging
- Webhook Replay: Allow users to replay failed webhook deliveries
- Webhook Signatures UI: Display signature verification examples in admin panel
- Batch Webhooks: Option to batch multiple events into one webhook call
Production Considerations
- Job Queue: Replace
setTimeoutwith Bull/BullMQ for production-grade retry handling - Rate Limiting: Add rate limiting to prevent webhook abuse
- Monitoring: Set up alerts for webhook failures
- Webhook Analytics: Track delivery success rates per webhook
🐛 Troubleshooting
Webhook Not Triggering
- ✅ Check webhook status is
ACTIVE - ✅ Verify the event is subscribed in webhook configuration
- ✅ Check server logs for any errors
- ✅ Ensure the event is actually occurring in your app
Webhook Failing
- ✅ Verify webhook URL is accessible
- ✅ Check response status (must be 2xx for success)
- ✅ Review timeout settings (default: 30s)
- ✅ Check webhook delivery history in admin panel
Signature Verification Failing
- ✅ Ensure you're using the correct secret key
- ✅ Verify you're signing only the
datafield, not the entire payload - ✅ Check that JSON serialization is consistent
- ✅ Use constant-time comparison to prevent timing attacks
Last Updated: November 23, 2025 Version: 1.0.0