Skip to main content

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 DRAFTACTIVE
  • 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 (from PUBLISHED)
  • 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-form or POST /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

  1. Create a webhook in your admin panel
  2. Set up a webhook receiver endpoint (or use a service like webhook.site)
  3. 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 FAILED after 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

  1. Implement milestone.reached: Add logic to track and trigger milestones
  2. Add Webhook Logging: Log all webhook deliveries for debugging
  3. Webhook Replay: Allow users to replay failed webhook deliveries
  4. Webhook Signatures UI: Display signature verification examples in admin panel
  5. Batch Webhooks: Option to batch multiple events into one webhook call

Production Considerations

  1. Job Queue: Replace setTimeout with Bull/BullMQ for production-grade retry handling
  2. Rate Limiting: Add rate limiting to prevent webhook abuse
  3. Monitoring: Set up alerts for webhook failures
  4. Webhook Analytics: Track delivery success rates per webhook

🐛 Troubleshooting

Webhook Not Triggering

  1. ✅ Check webhook status is ACTIVE
  2. ✅ Verify the event is subscribed in webhook configuration
  3. ✅ Check server logs for any errors
  4. ✅ Ensure the event is actually occurring in your app

Webhook Failing

  1. ✅ Verify webhook URL is accessible
  2. ✅ Check response status (must be 2xx for success)
  3. ✅ Review timeout settings (default: 30s)
  4. ✅ Check webhook delivery history in admin panel

Signature Verification Failing

  1. ✅ Ensure you're using the correct secret key
  2. ✅ Verify you're signing only the data field, not the entire payload
  3. ✅ Check that JSON serialization is consistent
  4. ✅ Use constant-time comparison to prevent timing attacks

Last Updated: November 23, 2025 Version: 1.0.0