Custom Webhook Integration
Send events to your own backend or third-party APIs with control over data transformation, batching, and delivery.
Configuration
Section titled “Configuration”Configure the custom webhook provider with your endpoint and optional settings:
providers: { custom: { enabled: true, settings: { // Required endpoint: 'https://api.yourapp.com/analytics',
// Optional headers headers: { 'Authorization': 'Bearer YOUR_TOKEN', 'Content-Type': 'application/json', 'X-Store-ID': 'store-123' },
// Batching settings batchSize: 10, // Events per batch batchIntervalMs: 5000, // Max time before sending
// Retry configuration maxRetries: 3, retryDelayMs: 1000, // Exponential backoff: 1s, 2s, 4s
// Transform events before sending transformFunction: (event) => { return { ...event, app_version: '1.0.0', environment: 'production', timestamp: new Date().toISOString() }; } } }}Configuration Options
Section titled “Configuration Options”| Option | Type | Required | Default | Description |
|---|---|---|---|---|
endpoint | string | Yes | - | The URL of your backend endpoint that receives analytics events |
headers | object | No | {} | Custom HTTP headers to include in each request (e.g., auth tokens) |
batchSize | number | No | 10 | Number of events to accumulate before sending a batch |
batchIntervalMs | number | No | 5000 | Maximum milliseconds to wait before sending a batch, regardless of size |
maxRetries | number | No | 3 | Number of times to retry failed requests |
retryDelayMs | number | No | 1000 | Initial delay in milliseconds for retry attempts (exponential backoff) |
transformFunction | function | No | - | Function to transform each event before sending (receives event, returns modified event) |
Request Format
Section titled “Request Format”Events are sent as POST requests in batches to your endpoint:
POST https://api.yourapp.com/analyticsContent-Type: application/jsonAuthorization: Bearer YOUR_TOKEN
{ "events": [ { "event": "dl_add_to_cart", "event_id": "sess_123_1_1234567890", "event_time": "2025-01-12T10:30:00Z", "ecommerce": { "items": [ { "item_id": "SKU123", "item_name": "Product Name", "price": 29.99, "quantity": 1 } ], "value": 29.99, "currency": "USD" }, "user_properties": { "user_id": "user_123", "session_id": "sess_123", "country": "US" }, "attribution": { "source": "google", "medium": "cpc", "campaign": "summer_sale" } }, { "event": "dl_view_item", "event_id": "sess_123_2_1234567891", "event_time": "2025-01-12T10:30:02Z", ... } ], "batch_info": { "size": 2, "timestamp": "2025-01-12T10:30:05Z", "source": "next-campaign-cart" }}Request Body Fields
Section titled “Request Body Fields”| Field | Type | Description |
|---|---|---|
events | array | Array of event objects (max 100 per batch) |
batch_info.size | number | Number of events in this batch |
batch_info.timestamp | string | ISO 8601 timestamp when batch was sent |
batch_info.source | string | Always “next-campaign-cart” |
Response Expected
Section titled “Response Expected”Your endpoint should return appropriate HTTP status codes:
200 OK- Events processed successfully201 Created- Events created successfully202 Accepted- Events accepted for processing (recommended)4xx- Client error (SDK will retry based on specific status)5xx- Server error (SDK will retry with exponential backoff)
Response Body (Optional)
Section titled “Response Body (Optional)”While not required, you can return a response body:
{ "success": true, "message": "Analytics events received", "batch_id": "batch_abc123xyz", "events_processed": 2}Batching Behavior
Section titled “Batching Behavior”Events are batched to reduce network requests and server load. Batches are sent when:
- Size Threshold:
batchSizeevents have been queued - Time Threshold:
batchIntervalMsmilliseconds have elapsed since the first event in the batch - Page Unload: Immediately before navigation or window close to ensure data is not lost
- Manual Flush: When explicitly requested through the SDK API (if available)
Example Batching Timeline
Section titled “Example Batching Timeline”With batchSize: 10 and batchIntervalMs: 5000:
Time Event Batch Status0ms Event 1 arrives Queue: [1] → Start 5s timer100ms Event 2 arrives Queue: [1, 2]500ms Event 3 arrives Queue: [1, 2, 3]1000ms Event 4 arrives Queue: [1, 2, 3, 4]1500ms Event 5 arrives Queue: [1, 2, 3, 4, 5]2000ms Event 6 arrives Queue: [1, 2, 3, 4, 5, 6]2500ms Event 7 arrives Queue: [1, 2, 3, 4, 5, 6, 7]3000ms Event 8 arrives Queue: [1, 2, 3, 4, 5, 6, 7, 8]3500ms Event 9 arrives Queue: [1, 2, 3, 4, 5, 6, 7, 8, 9]4000ms Event 10 arrives Queue: [1-10] → SEND BATCH (size threshold met)4050ms Event 11 arrives Queue: [11] → Start new 5s timer5000ms (5s elapsed) Queue: [11-15] → SEND BATCH (time threshold met)Retry Logic
Section titled “Retry Logic”Failed requests retry with exponential backoff to prevent overwhelming your server during temporary outages.
Retry Schedule
Section titled “Retry Schedule”- Attempt 1: Immediate, no delay
- Attempt 2: Wait
retryDelayMs(default: 1s) - Attempt 3: Wait
retryDelayMs * 2(default: 2s) - Attempt 4: Wait
retryDelayMs * 4(default: 4s)
After maxRetries attempts, events are dropped and logged.
Example with Default Settings
Section titled “Example with Default Settings”Time Event Status0ms Send batch Fails (500 error)0ms Schedule retry 11000ms Retry attempt 1 Fails (500 error)1000ms Schedule retry 23000ms Retry attempt 2 Fails (503 timeout)3000ms Schedule retry 37000ms Retry attempt 3 Succeeds (200 OK) Batch sent!Retry Configuration Examples
Section titled “Retry Configuration Examples”Aggressive Retries (for critical analytics):
maxRetries: 5,retryDelayMs: 500, // 0.5s, 1s, 2s, 4s, 8s = 15.5s totalConservative Retries (for high-volume events):
maxRetries: 2,retryDelayMs: 2000, // 2s, 4s = 6s totalNo Retries (if endpoint is highly available):
maxRetries: 0, // Send once, drop on failureTransform Function
Section titled “Transform Function”The transformFunction option allows you to modify, filter, or enrich events before they’re sent to your backend. This function is called once for each event.
Basic Transform Function
Section titled “Basic Transform Function”transformFunction: (event) => { // Add custom fields event.app_version = '1.0.0'; event.environment = 'production'; event.server_timestamp = Date.now();
// Filter sensitive data if (event.user_properties) { delete event.user_properties.customer_phone; }
// Add business context if (event.event === 'dl_purchase') { event.custom_order_type = 'online'; event.fulfillment_center = 'US-WEST'; }
return event;}Advanced Transform Examples
Section titled “Advanced Transform Examples”Conditional Field Addition
Section titled “Conditional Field Addition”transformFunction: (event) => { // Add fields only for specific events if (event.event === 'dl_purchase') { event.revenue_category = event.ecommerce?.value > 100 ? 'high' : 'normal'; }
// Add user tier information if (event.user_properties?.user_id) { event.user_tier = calculateUserTier(event.user_properties.user_id); }
return event;}Data Validation and Sanitization
Section titled “Data Validation and Sanitization”transformFunction: (event) => { // Ensure required fields if (!event.event_time) { event.event_time = new Date().toISOString(); }
// Sanitize strings if (event.user_properties?.session_id) { event.user_properties.session_id = event.user_properties.session_id.replace(/[^a-zA-Z0-9_-]/g, ''); }
// Convert currencies if (event.ecommerce?.currency === 'EUR') { event.ecommerce.value_usd = event.ecommerce.value * 0.92; }
return event;}PII Redaction
Section titled “PII Redaction”transformFunction: (event) => { const sensitiveFields = ['email', 'phone', 'ssn', 'credit_card'];
if (event.user_properties) { sensitiveFields.forEach(field => { if (event.user_properties[field]) { delete event.user_properties[field]; } }); }
return event;}Event Sampling
Section titled “Event Sampling”transformFunction: (event) => { // Sample non-purchase events at 10% to reduce volume if (event.event !== 'dl_purchase') { if (Math.random() > 0.1) { return null; // Drop this event } }
return event;}Regional Routing
Section titled “Regional Routing”transformFunction: (event) => { const region = getUserRegion(); event.endpoint_region = region;
// Set processing priority event.processing_priority = event.event === 'dl_purchase' ? 'high' : 'normal';
// Add regional context event.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
return event;}Use Cases
Section titled “Use Cases”Data Warehouse Integration
Section titled “Data Warehouse Integration”Forward events to your data warehouse for long-term analysis and reporting:
endpoint: 'https://api.yourdata.com/ingest/analytics',headers: { 'Authorization': 'Bearer warehouse-token', 'X-Dataset-ID': 'campaign-cart-events'},batchSize: 50, // Larger batches for warehouse efficiencybatchIntervalMs: 10000,transformFunction: (event) => { return { ...event, warehouse_partition: new Date().toISOString().split('T')[0], ingestion_source: 'campaign-cart' };}Supported: Snowflake, BigQuery, Redshift, Databricks, etc.
Custom Analytics Platform
Section titled “Custom Analytics Platform”Send events to your own analytics infrastructure:
endpoint: 'https://analytics.yourcompany.com/api/v1/events',headers: { 'Authorization': 'Bearer api-key', 'X-App-ID': 'campaign-cart'},maxRetries: 3,transformFunction: (event) => { return { ...event, event_version: '2.0', company_id: 'comp_123', received_at: new Date().toISOString() };}Marketing Automation
Section titled “Marketing Automation”Trigger marketing workflows based on analytics events:
endpoint: 'https://api.marketing-platform.com/webhooks/events',headers: { 'Authorization': 'Bearer webhook-key', 'X-Integration-ID': 'cart-system'},batchSize: 1, // Send events immediatelybatchIntervalMs: 100,transformFunction: (event) => { // Only send customer conversion events if (!['dl_purchase', 'dl_view_item'].includes(event.event)) { return null; }
return { ...event, trigger_type: event.event === 'dl_purchase' ? 'purchase' : 'view', automation_enabled: true };}Platforms: HubSpot, Klaviyo, Braze, Marketo, etc.
Real-time Processing
Section titled “Real-time Processing”Process events in real-time systems for immediate action:
endpoint: 'https://streaming.yourcompany.com/events',headers: { 'Authorization': 'Bearer stream-key'},batchSize: 5,batchIntervalMs: 1000,transformFunction: (event) => { return { ...event, processing_timestamp: Date.now(), priority: ['dl_purchase', 'dl_error'].includes(event.event) ? 'high' : 'normal' };}Platforms: Kafka, RabbitMQ, Apache Pulsar, AWS Kinesis, etc.
Multi-region Distribution
Section titled “Multi-region Distribution”Send different events to different regional endpoints:
endpoint: 'https://api.yourapp.com/analytics',transformFunction: (event) => { // Determine user region const region = getUserRegion(); // 'us', 'eu', 'apac'
return { ...event, target_region: region, regional_endpoint: `https://api-${region}.yourapp.com/analytics`, processing_priority: event.event === 'dl_purchase' ? 'high' : 'normal', is_critical_event: ['dl_purchase', 'dl_error'].includes(event.event) };}Example: Multi-endpoint Setup
Section titled “Example: Multi-endpoint Setup”For advanced scenarios where you need to send events to multiple endpoints or route them based on event type:
import { useAnalytics } from '@campaign-cart/analytics';
const analytics = useAnalytics({ providers: { custom: { enabled: true, settings: { endpoint: 'https://api.yourapp.com/analytics', headers: { 'Authorization': 'Bearer YOUR_TOKEN', 'Content-Type': 'application/json' }, batchSize: 10, batchIntervalMs: 5000, maxRetries: 3, retryDelayMs: 1000,
// Advanced routing based on event type and attributes transformFunction: (event) => { const transformed = { ...event, app_version: '1.0.0', environment: process.env.NODE_ENV };
// Route purchase events to high-priority endpoint if (event.event === 'dl_purchase') { transformed.endpoint_priority = 'high'; transformed.endpoint_type = 'purchases'; transformed.requires_acknowledgment = true; }
// Route behavioral events normally else if (['dl_view_item', 'dl_add_to_cart', 'dl_remove_from_cart'].includes(event.event)) { transformed.endpoint_priority = 'normal'; transformed.endpoint_type = 'behaviors'; }
// Route errors to special endpoint else if (event.event === 'dl_error') { transformed.endpoint_priority = 'critical'; transformed.endpoint_type = 'errors'; transformed.alert_on_receipt = true; }
// Add regional information const userRegion = detectUserRegion(); transformed.region = userRegion; transformed.regional_endpoint = `https://api-${userRegion}.yourapp.com/analytics`;
return transformed; } } } }});
// Helper function to detect user regionfunction detectUserRegion() { if (typeof window === 'undefined') return 'default';
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
if (timezone.includes('America')) return 'us'; if (timezone.includes('Europe')) return 'eu'; if (timezone.includes('Asia') || timezone.includes('Australia')) return 'apac';
return 'default';}
// In your componentanalytics.track('dl_purchase', { ecommerce: { value: 99.99, currency: 'USD', items: [ { item_id: 'SKU123', item_name: 'Product', quantity: 1, price: 99.99 } ] }});Troubleshooting
Section titled “Troubleshooting”Events Not Being Sent
Section titled “Events Not Being Sent”Problem: Analytics events are not reaching your endpoint.
Solutions:
-
Check endpoint URL: Ensure the
endpointis correct and accessible// Test in browser consolefetch('https://api.yourapp.com/analytics', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ test: true })}) -
Verify CORS headers: Ensure your endpoint allows requests from your domain
Access-Control-Allow-Origin: *Access-Control-Allow-Methods: POST, OPTIONSAccess-Control-Allow-Headers: Content-Type, Authorization -
Check browser console: Look for network errors (404, 403, CORS errors)
- Open DevTools → Network tab → Filter for your endpoint
- Check Request/Response headers and body
-
Verify configuration: Ensure custom provider is enabled
providers: {custom: {enabled: true, // Must be truesettings: { ... }}}
401 Unauthorized Errors
Section titled “401 Unauthorized Errors”Problem: Requests are rejected with 401 status.
Solutions:
-
Check authorization header:
headers: {'Authorization': 'Bearer YOUR_VALID_TOKEN' // Use actual token} -
Verify token expiration: Ensure tokens are refreshed periodically
headers: {'Authorization': `Bearer ${getValidToken()}`} -
Test endpoint authentication:
Terminal window curl -X POST https://api.yourapp.com/analytics \-H "Authorization: Bearer YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"events": [], "batch_info": {}}'
Retries Not Working
Section titled “Retries Not Working”Problem: Failed requests are not being retried.
Solutions:
-
Check retry configuration:
maxRetries: 3, // Must be > 0retryDelayMs: 1000 // Must be positive -
Check status codes: SDK only retries on 5xx, timeouts, and network errors
- 4xx errors (except some specific cases) are not retried
- 2xx responses are considered success
-
Monitor retry logs: Enable debug logging (if available)
// Check browser DevTools console for messages like:// "Analytics: Retrying batch (attempt 2 of 3)..."
High Event Loss
Section titled “High Event Loss”Problem: Events are being dropped before reaching your endpoint.
Solutions:
-
Increase batch time limits:
// Give more time before droppingbatchIntervalMs: 10000, // Increased from 5000maxRetries: 5 // More retry attempts -
Implement page unload handler (SDK should handle this automatically)
-
Check transform function: Ensure it’s not filtering out events
transformFunction: (event) => {console.log('Event:', event); // Debug what's being sentreturn event;}
Transform Function Issues
Section titled “Transform Function Issues”Problem: Events are being modified incorrectly or transform function has errors.
Solutions:
-
Add error handling:
transformFunction: (event) => {try {// Your transform logicreturn {...event,custom_field: calculateValue(event)};} catch (error) {console.error('Transform error:', error);return event; // Return original if transform fails}} -
Validate return value: Always return an event object or null
transformFunction: (event) => {// WRONG: return undefined// CORRECT: return event or nullif (shouldDropEvent(event)) {return null;}return modifiedEvent;} -
Test transform function independently:
const testEvent = { event: 'dl_view_item', ... };const result = transformFunction(testEvent);console.log('Transform result:', result);
Endpoint Timeouts
Section titled “Endpoint Timeouts”Problem: Requests are timing out and being retried excessively.
Solutions:
-
Reduce batch size:
batchSize: 5, // Smaller payloads = faster processing -
Increase request timeout (if configurable):
requestTimeoutMs: 10000 // 10 second timeout -
Check backend performance:
Terminal window # Monitor endpoint response timescurl -w "@curl-format.txt" -o /dev/null -s \https://api.yourapp.com/analytics -
Verify network connectivity: Check if your server can reach the endpoint
Duplicate Events at Endpoint
Section titled “Duplicate Events at Endpoint”Problem: Events appear to be received multiple times.
Solutions:
-
Implement idempotency:
- Use
event_idfield (already unique per event) - Store processed event IDs to detect duplicates
// In your backendconst processedEventIds = new Set();if (processedEventIds.has(event.event_id)) {return { success: true }; // Already processed}processedEventIds.add(event.event_id);// Process event... - Use
-
Check batch_info: Events in same batch have unique IDs
const eventIds = batch.events.map(e => e.event_id);const uniqueIds = new Set(eventIds);if (uniqueIds.size !== eventIds.length) {// Handle duplicate detection} -
Verify retry handling: Ensure retried batches aren’t duplicated
- Use batch IDs if implementing retry deduplication
Backend Not Receiving Headers
Section titled “Backend Not Receiving Headers”Problem: Custom headers aren’t appearing in requests.
Solutions:
-
Check header configuration:
headers: {'Authorization': 'Bearer token','X-Custom-Header': 'value'} -
Verify CORS headers (for browser requests):
Access-Control-Allow-Headersmust include custom header names
Access-Control-Allow-Headers: Authorization, Content-Type, X-Custom-Header -
Check header case sensitivity: Some servers normalize headers
// Both work, but be consistent'Authorization': 'Bearer token''authorization': 'Bearer token'
Memory Leaks with Events
Section titled “Memory Leaks with Events”Problem: Memory usage increases continuously.
Solutions:
-
Limit batch size:
batchSize: 20, // Don't queue too manybatchIntervalMs: 3000 // Send more frequently -
Implement event sampling in transform function:
transformFunction: (event) => {// Sample down high-volume eventsif (event.event === 'dl_page_view' && Math.random() > 0.1) {return null; // Drop 90% of page views}return event;} -
Check for event listener leaks: Ensure analytics instance is properly cleaned up
Best Practices
Section titled “Best Practices”- Always return a 2xx status from your endpoint when events are received
- Make endpoints idempotent using event IDs to handle retries safely
- Log all received events for debugging and auditing
- Monitor endpoint performance to catch issues early
- Use appropriate batch sizes based on your network and server capacity
- Implement rate limiting on your endpoint to prevent abuse
- Validate incoming data to ensure it matches your schema
- Archive analytics data for historical analysis
- Alert on errors (via email, Slack, etc.) for critical endpoints
- Version your endpoint (e.g.,
/api/v1/analytics) for backward compatibility