> ## Documentation Index
> Fetch the complete documentation index at: https://funnelfox.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks setup & configuration

> Configure FunnelFox webhooks to receive real-time event notifications for purchases, subscriptions, and funnel interactions.

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](/develop/webhooks#subscription-access-via-webhooks).

## 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.

```mermaid theme={null}
sequenceDiagram
    participant User
    participant FunnelFox
    participant Your Server
    
    User->>FunnelFox: Completes funnel
    FunnelFox->>Your Server: POST webhook event
    Your Server->>Your Server: Process event
    Your Server->>FunnelFox: 200 OK
```

## Quick Start

<Steps>
  <Step title="Configure Endpoint">
    Add your webhook URL in [Project Settings](/dashboard/settings#webhooks):

    * **Production URL**: For live events
    * **Sandbox URL**: For test events (optional)
  </Step>

  <Step title="Verify Requests">
    Validate the `Fox-Secret-Key` header matches your project's secret key
  </Step>

  <Step title="Handle Events">
    Process the JSON payload and respond with 2xx status within 10 seconds
  </Step>

  <Step title="Implement Retries">
    Handle potential duplicate events and implement idempotency
  </Step>
</Steps>

## Event Types

FunnelFox sends webhooks for these events:

<CardGroup cols={2}>
  <Card title="Funnel Events" icon="route">
    * `onboarding.started` - User begins funnel
    * `onboarding.completed` - User reaches finish screen
    * `profile.updated` - Email captured or updated
  </Card>

  <Card title="Payment Events" icon="credit-card">
    * `purchase.completed` -  User has completed a purchase or when recurrent/upsell purchase happen
  </Card>

  <Card title="Subscription Events" icon="rotate">
    * `subscription.created` - New subscription
    * `subscription.trialing` - Trial period started
    * `subscription.active` - Subscription activated
    * `subscription.cycle` - Renewal processed
    * `subscription.paused` - Billing paused
    * `subscription.resumed` - Billing resumed
    * `subscription.cancelled` - Subscription cancelled
    * `subscription.auto_renew_off` - Auto-renewal turned off
    * `subscription.unpaid` - Payment failed
  </Card>
</CardGroup>

<Tip>Use `subscription.` webhooks to grant new customers [subscription access](/develop/webhooks#subscription-access-via-webhooks).</Tip>

See the [Events Reference](/api-reference/webhook-reference/onboardingstarted) for complete payload schemas.

## Security & Verification

Every webhook request includes a `Fox-Secret-Key` header containing your project's
secret key. Always verify this header to ensure requests are from FunnelFox.

```javascript theme={null}
// Express.js example
app.post('/webhook', (req, res) => {
  const secret = req.headers['fox-secret-key'];
  
  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');
});
```

<Warning>
  **Security**: Never expose your Fox-Secret-Key in client-side code or public
  repositories. Store it securely in environment variables.
</Warning>

## Payload Structure

All webhook events follow this base structure:

```json theme={null}
{
  "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

| Field        | Type    | Description                             |
| ------------ | ------- | --------------------------------------- |
| `id`         | string  | Unique event identifier                 |
| `type`       | string  | Event type (e.g., "purchase.completed") |
| `created_at` | integer | Unix timestamp in seconds               |
| `is_sandbox` | boolean | Whether this is a test event            |
| `location`   | object  | Funnel and experiment context           |
| `profile`    | object  | User information and geo data           |
| `data`       | object  | Event-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 theme={null}
# 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-Key') != 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:

```javascript theme={null}
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

<Info>
  Failed webhooks are retried for up to 2 weeks. Ensure your endpoint can
  handle events that arrive significantly after they occurred.
</Info>

## Testing Webhooks

### Local Development

Use tools like ngrok to expose your local server:

```bash theme={null}
# 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:

* [Webhookah](https://webhookah.com/) - Test your webhooks in real-time
* [Webhook.site](https://webhook.site) - Instant test endpoints
* [RequestBin](https://requestbin.com) - Inspect webhook payloads
* [Hookdeck](https://hookdeck.com) - Development webhook infrastructure

### 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

## Subscription Access via Webhooks

You can grant new customers subscription access using `subscription.` webhooks such as [subscription.trialing](/api-reference/webhook-reference/subscriptiontrialing) or [subscription.active](/api-reference/webhook-reference/subscriptionactive).

These webhooks are optimized for reliability and have the lowest delivery delay, making them ideal for managing subscription access in real time.

<Warning> Avoid using `purchase.completed` for this purpose. It’s optimized for tracking purchases rather than managing subscription states.</Warning>

## Common Use Cases

### CRM Integration

Sync funnel completions to your CRM:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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

<CardGroup cols={2}>
  <Card title="Process Asynchronously" icon="clock">
    Queue events for processing instead of handling synchronously. This ensures
    fast responses and prevents timeouts.
  </Card>

  <Card title="Implement Monitoring" icon="chart-line">
    Track webhook processing metrics: success rates, processing time, and
    queue depth to catch issues early.
  </Card>

  <Card title="Handle Failures Gracefully" icon="shield">
    Implement circuit breakers and fallback mechanisms. Don't let webhook
    failures break critical flows.
  </Card>

  <Card title="Store Raw Events" icon="database">
    Archive raw webhook payloads for debugging, replay capabilities, and
    audit trails.
  </Card>
</CardGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Webhooks not being received">
    * Verify your endpoint URL is publicly accessible (not localhost)
    * Check the Fox-Secret-Key header is being validated correctly
    * Ensure your server responds within 10 seconds
    * Confirm webhooks are configured in Project Settings
  </Accordion>

  <Accordion title="Receiving duplicate events">
    This is expected behavior due to retry logic. Implement idempotency using
    the event `id` field to handle duplicates gracefully.
  </Accordion>

  <Accordion title="Events arriving out of order">
    Network delays and retries can cause events to arrive out of sequence.
    Use the `created_at` timestamp to determine actual event order.
  </Accordion>

  <Accordion title="Missing data in payload">
    Some fields are only present for specific event types. Check the
    [Events Reference](/api-reference/webhook-reference/onboardingstarted) for complete schemas. The `data` field varies per event.
  </Accordion>

  <Accordion title="Sandbox vs Production events">
    Check the `is_sandbox` field to differentiate test events. Configure
    separate webhook URLs for sandbox and production in Project Settings.
  </Accordion>
</AccordionGroup>

## 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

* [Webhook Reference](/api-reference/webhook-reference/onboardingstarted) - Detailed event schemas
* [Project Settings](/dashboard/settings#webhooks) - Configure webhook endpoints
