Skip to Content
Webhooks

Webhooks

Send event data to any HTTPS endpoint with signed payloads and automatic retries. Webhooks let you integrate AppFunnel with your own backend, data warehouse, or any service that accepts HTTP requests.

You can create multiple webhook integrations per project, each pointing to a different endpoint with different event subscriptions.

Setup

Navigate to Settings > Integrations > Webhook in your project.

  1. Webhook URL: The HTTPS endpoint that will receive event payloads (e.g., https://api.yourapp.com/webhooks/appfunnel).
  2. Signing Secret: A secret string used to sign payloads. AppFunnel generates one for you, or you can provide your own. This is required.
  3. Event Selection: Choose which events to subscribe to, or subscribe to all events.
  4. Custom Headers (optional): Add custom HTTP headers that will be included in every request (e.g., Authorization: Bearer your-token).

Webhook Integration Setup Insert image: The Webhook integration settings page showing the Webhook URL input, Signing Secret field, event selection checkboxes, and Custom Headers section

Payload Format

Every webhook delivery sends a JSON payload with this structure:

{ "eventId": "V1StGXR8_Z5jdHi6B-myT", "event": "purchase.complete", "timestamp": "2026-03-10T12:00:00.000Z", "project": "proj_abc123", "data": { "email": "user@example.com", "amount": 2999, "currency": "USD", "subscriptionId": "sub_xyz789" } }

The data field contains event-specific information. Its shape varies by event type.

Headers

Every webhook request includes the following headers:

HeaderDescription
Content-TypeAlways application/json
X-Appfunnel-SignatureHMAC-SHA256 hex digest of the raw request body
X-Appfunnel-EventThe event type (e.g., purchase.complete)
X-Appfunnel-Event-IdUnique event ID for deduplication

Any custom headers you configured are also included.

Verifying Webhook Signatures

Every webhook request is signed using your signing secret. You should verify the signature before processing the payload to ensure the request is authentic.

Algorithm

  1. Read the raw request body as bytes. Do not parse JSON first.
  2. Compute HMAC-SHA256 of the raw body using your signing secret as the key.
  3. Hex-encode the result.
  4. Compare with the X-Appfunnel-Signature header using a constant-time comparison function.

Node.js Example

const crypto = require('crypto'); function verifySignature(rawBody, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } // Express handler (use express.raw() to get the raw body) app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-appfunnel-signature']; if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(req.body); // Handle event... res.status(200).send('OK'); });

Python Example

import hmac import hashlib def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected)

Always use a constant-time comparison function (like crypto.timingSafeEqual or hmac.compare_digest) to prevent timing attacks. Do not use === or == for signature comparison.

Retry Policy

If your endpoint does not return a 2xx status code, AppFunnel will retry the delivery:

  • 3 total attempts (1 initial + 2 retries)
  • Exponential backoff with a 5-second base delay (5s, 10s, 20s)
  • After 3 consecutive failures across all events, the webhook integration is automatically disabled via the circuit breaker and project members are notified by email

Return a 200 response as quickly as possible. Process the event asynchronously in your own system if the work is expensive.

Testing

When you save the webhook configuration, AppFunnel sends a test.ping event to your endpoint to verify connectivity. The test payload looks like:

{ "eventId": "test_ping", "event": "test.ping", "timestamp": "2026-03-10T12:00:00.000Z", "project": "test", "data": { "test": true } }

If your endpoint does not return 2xx within 5 seconds, the validation fails and the integration cannot be saved.

Last updated on