Webhooks allow your application to receive real-time notifications when specific events occur in FunnelFox. Instead of polling for changes, webhooks push data to your endpoint as events happen, enabling instant reactions to user actions, purchases, and subscription changes.

How Webhooks Work

When an event occurs in your funnel, FunnelFox sends an HTTP POST request to your configured endpoint with a JSON payload containing event details.

Quick Start

1

Configure Endpoint

Add your webhook URL in Project Settings:
  • Production URL: For live events
  • Sandbox URL: For test events (optional)
2

Verify Requests

Validate the Fox-Secret header matches your project’s secret key
3

Handle Events

Process the JSON payload and respond with 2xx status within 10 seconds
4

Implement Retries

Handle potential duplicate events and implement idempotency

Event Types

FunnelFox sends webhooks for these events:

Funnel Events

  • onboarding.started - User begins funnel
  • onboarding.completed - User reaches finish screen
  • profile.updated - Email captured or updated

Payment Events

  • purchase.completed - One-time payment success
  • subscription.created - New subscription
  • subscription.active - Subscription activated
  • subscription.cycle - Renewal processed
See the Events Reference for complete payload schemas.

Security & Verification

Every webhook request includes a Fox-Secret header containing your project’s secret key. Always verify this header to ensure requests are from FunnelFox.
// Express.js example
app.post('/webhook', (req, res) => {
  const secret = req.headers['fox-secret'];
  
  if (secret !== process.env.FUNNELFOX_SECRET) {
    return res.status(401).send('Unauthorized');
  }
  
  // Process webhook
  const event = req.body;
  console.log(`Received ${event.type} event`);
  
  res.status(200).send('OK');
});
Security: Never expose your Fox-Secret key in client-side code or public repositories. Store it securely in environment variables.

Payload Structure

All webhook events follow this base structure:
{
  "id": "evt_00000000000000000000000000",
  "type": "onboarding.completed",
  "created_at": 1704067200,
  "project_id": "00000000000000000000000000",
  "is_sandbox": false,
  "session_id": "ses_00000000000000000000000000",
  "location": {
    "funnel_id": "00000000000000000000000000",
    "funnel_version": 3,
    "origin": "https://example.fnlfx.com/welcome",
    "query": "?utm_source=google",
    "query_params": {
      "utm_source": "google"
    },
    "sandbox": false,
    "experiment_id": "exp_123",
    "experiment_title": "Pricing Test",
    "experiment_alias": "pricing-test"
  },
  "profile": {
    "id": "pro_00000000000000000000000000",
    "session_id": "ses_00000000000000000000000000",
    "country": "US",
    "ip": "192.0.2.1",
    "city": "New York",
    "time_zone": "America/New_York",
    "user_agent": "Mozilla/5.0...",
    "locale": "🇺🇸 English",
    "locale_code": "en_US"
  },
  "data": {
    // Event-specific data
  }
}

Key Fields

FieldTypeDescription
idstringUnique event identifier
typestringEvent type (e.g., “purchase.completed”)
created_atintegerUnix timestamp in seconds
is_sandboxbooleanWhether this is a test event
locationobjectFunnel and experiment context
profileobjectUser information and geo data
dataobjectEvent-specific payload

Handling Webhooks

Response Requirements

Your endpoint must:
  • Return a 2xx status code (200-299)
  • Respond within 10 seconds
  • Accept application/json content type
# Python Flask example
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    # Verify secret
    if request.headers.get('Fox-Secret') != FUNNELFOX_SECRET:
        return jsonify({'error': 'Unauthorized'}), 401
    
    event = request.json
    
    # Process asynchronously (recommended)
    queue_for_processing(event)
    
    # Respond immediately
    return jsonify({'status': 'received'}), 200

Idempotency

Events may be delivered multiple times. Use the event id to ensure idempotent processing:
const processedEvents = new Set();

function handleWebhook(event) {
  if (processedEvents.has(event.id)) {
    console.log(`Duplicate event ${event.id}, skipping`);
    return;
  }
  
  processedEvents.add(event.id);
  // Process event...
}

Retry Logic

FunnelFox implements exponential backoff with up to 30 retry attempts for failed webhook deliveries.

Retry Schedule

The delay between retries follows this formula:
delay(n) = n^4 + 15 + random(0-29) × (n+1) seconds
This results in approximately:
  • Attempt 1: ~15-45 seconds
  • Attempt 2: ~30-90 seconds
  • Attempt 3: ~1-3 minutes
  • Attempt 10: ~3-5 hours
  • Attempt 30: ~10-12 days
Failed webhooks are retried for up to 2 weeks. Ensure your endpoint can handle events that arrive significantly after they occurred.

Testing Webhooks

Local Development

Use tools like ngrok to expose your local server:
# Install ngrok
npm install -g ngrok

# Expose local port 3000
ngrok http 3000

# Use the HTTPS URL in FunnelFox settings
# https://abc123.ngrok.io/webhook

Webhook Testing Tools

Popular webhook testing services:

Debugging Tips

  1. Log everything initially: Record full payloads to understand data
  2. Use sandbox mode: Test with sandbox funnels to avoid real charges
  3. Implement graceful failures: Queue events for retry if processing fails
  4. Monitor endpoint health: Track response times and error rates

Common Use Cases

CRM Integration

Sync funnel completions to your CRM:
if (event.type === 'onboarding.completed') {
  const replies = event.data.replies;
  
  await crm.createLead({
    email: replies.email,
    name: replies.name,
    source: 'FunnelFox',
    funnel: event.location.funnel_id,
    customFields: replies
  });
}

Email Automation

Trigger email sequences based on user actions:
switch(event.type) {
  case 'profile.updated':
    await email.addToList(event.data.email, 'newsletter');
    break;
    
  case 'purchase.completed':
    await email.sendReceipt(event.data);
    await email.startOnboarding(event.data.email);
    break;
    
  case 'subscription.cancelled':
    await email.sendWinbackCampaign(event.data.email);
    break;
}

Analytics Tracking

Send events to your analytics platform:
analytics.track({
  userId: event.profile.id,
  event: event.type,
  properties: {
    funnel_id: event.location.funnel_id,
    experiment: event.location.experiment_alias,
    revenue: event.data.amount,
    currency: event.data.currency
  }
});

Subscription Management

Keep subscription states synchronized:
if (event.type.startsWith('subscription.')) {
  await database.updateSubscription({
    customerId: event.data.customer_id,
    status: event.type.split('.')[1],
    productId: event.data.product_id,
    nextBillingDate: event.data.next_billing_date
  });
}

Best Practices

Process Asynchronously

Queue events for processing instead of handling synchronously. This ensures fast responses and prevents timeouts.

Implement Monitoring

Track webhook processing metrics: success rates, processing time, and queue depth to catch issues early.

Handle Failures Gracefully

Implement circuit breakers and fallback mechanisms. Don’t let webhook failures break critical flows.

Store Raw Events

Archive raw webhook payloads for debugging, replay capabilities, and audit trails.

Troubleshooting

Rate Limits

FunnelFox doesn’t impose rate limits on webhook deliveries. However:
  • Events are sent as they occur (no batching)
  • High-volume funnels may generate many simultaneous requests
  • Ensure your endpoint can handle your expected traffic

Next Steps