Skip to main content
Webhooks let FlexyPe notify your systems the moment something happens across any FlexyPe product — a customer abandoning a session, viewing a product, or completing a purchase for the first time. Instead of polling for updates, your endpoint receives a POST request with the event payload as soon as it fires.

How it works

  1. You configure a webhook endpoint URL in your FlexyPe dashboard
  2. FlexyPe sends a POST request to that URL whenever a subscribed event fires
  3. Your endpoint processes the payload and returns HTTP 200
  4. If delivery fails, FlexyPe retries automatically

Setting up a webhook

Go to Settings → Webhooks in your FlexyPe dashboard. When creating an endpoint, you’ll provide:
  • Endpoint URL — the HTTPS URL where FlexyPe should deliver events
  • Secret — a string you create, used to verify that requests come from FlexyPe
  • Events — the specific events you want to subscribe to
Only the events you select will trigger deliveries to your endpoint. Subscribe only to what your integration needs to avoid unnecessary traffic.

Request headers

Every webhook request includes these headers:
HeaderDescription
X-Flexype-AuthorizationYour webhook secret — validate this to confirm the request is from FlexyPe
X-Webhook-VersionThe webhook payload version (e.g. 2026-05)
X-Event-IdUnique identifier for this specific event occurrence

Verifying webhooks

Before processing any payload, validate the X-Flexype-Authorization header against the secret you set when configuring the webhook.
const secret = process.env.FLEXYPE_WEBHOOK_SECRET;
const incoming = req.headers['x-flexype-authorization'];

if (incoming !== secret) {
  return res.status(401).send('Unauthorized');
}

// Safe to process the payload
Always verify X-Flexype-Authorization before processing a webhook payload. Skip this check and anyone can send arbitrary data to your endpoint.
Store your webhook secret in an environment variable — never hardcode it in your source code or commit it to version control.

Responding to webhooks

Your endpoint must return HTTP 200 within 10 seconds to acknowledge receipt.
Return 200 immediately, then process the payload asynchronously — add it to a queue or background job rather than doing the work inline. This keeps you well within the timeout window.
Any non-200 response, or no response within 10 seconds, is treated as a delivery failure and triggers a retry.

Retry logic

When a delivery fails, FlexyPe retries three times before marking the webhook as failed:
AttemptDelay from previous failure
1st retry1 minute
2nd retry5 minutes
3rd retry (final)15 minutes
You can check delivery status and see failure reasons in the Webhooks section of your dashboard.
If all retry attempts fail, the webhook is marked as failed. You can manually retry individual failed deliveries from the dashboard at any time.

Idempotency

Each event has a unique X-Event-Id header. Use this value to deduplicate events — retried deliveries carry the same X-Event-Id as the original attempt, so you can safely ignore duplicates.
Always implement idempotency checks. Due to retries, your endpoint may receive the same event more than once.
const processedEvents = new Set();

app.post('/webhook', (req, res) => {
  const eventId = req.headers['x-event-id'];

  if (processedEvents.has(eventId)) {
    return res.status(200).json({ received: true });
  }

  processedEvents.add(eventId);
  // Process the webhook payload...
});
In production, persist processed event IDs in a database rather than an in-memory Set, so deduplication survives server restarts.

Testing webhooks

Use the Test button on your configured webhook in the FlexyPe dashboard to send a sample payload to your endpoint. Do this before going live to confirm your verification logic and response handling are working correctly.
Test your endpoint thoroughly before processing production events — this helps catch issues with verification, response timing, and payload parsing early.

Implementation example

A complete webhook handler that verifies the request, deduplicates retries, and processes the payload asynchronously:
import express from 'express';

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.FLEXYPE_WEBHOOK_SECRET;
const processedEvents = new Set(); // Use a database in production

app.post('/webhook', (req, res) => {
  // 1. Verify the request is from FlexyPe
  const incoming = req.headers['x-flexype-authorization'];
  if (incoming !== WEBHOOK_SECRET) {
    return res.status(401).send('Unauthorized');
  }

  // 2. Acknowledge receipt immediately
  const eventId = req.headers['x-event-id'];
  res.status(200).json({ received: true });

  // 3. Deduplicate retried deliveries
  if (processedEvents.has(eventId)) {
    return;
  }
  processedEvents.add(eventId);

  // 4. Process asynchronously
  setImmediate(() => {
    const { event_type, event_time } = req.body;
    console.log(`Processing event: ${event_type} at ${event_time}`);
    // Your business logic here...
  });
});

app.listen(3000);
import os
from flask import Flask, request, jsonify
from threading import Thread

app = Flask(__name__)

WEBHOOK_SECRET = os.environ.get('FLEXYPE_WEBHOOK_SECRET')
processed_events = set()  # Use a database in production

def process_event(payload):
    event_type = payload.get('event_type')
    event_time = payload.get('event_time')
    print(f'Processing event: {event_type} at {event_time}')
    # Your business logic here...

@app.route('/webhook', methods=['POST'])
def webhook():
    # 1. Verify the request is from FlexyPe
    incoming = request.headers.get('X-Flexype-Authorization')
    if incoming != WEBHOOK_SECRET:
        return 'Unauthorized', 401

    event_id = request.headers.get('X-Event-Id')

    # 2. Acknowledge receipt immediately
    response = jsonify({'received': True})

    # 3. Deduplicate retried deliveries
    if event_id in processed_events:
        return response
    processed_events.add(event_id)

    # 4. Process asynchronously
    payload = request.get_json()
    Thread(target=process_event, args=(payload,)).start()

    return response

if __name__ == '__main__':
    app.run(port=3000)

Available events

EventTrigger
Abandoned Checkout Recovery (ACR)Fires 15 minutes after a checkout session opens if the order is not completed
Abandoned Session Recovery (ASR)Visitor browses without starting checkout; fires after 15 minutes of inactivity
Collection ViewedCustomer views a collection page
Product ViewedCustomer views a product page
New CustomerFirst-time user logs in via FlexyPass and a new store account is created

Best practices

  • Use HTTPS — only HTTPS endpoints are accepted
  • Return 200 quickly — hand off processing to a background job to avoid timeouts
  • Check idempotency — use X-Event-Id to handle retried deliveries without double-processing
  • Store your secret securely — use environment variables; never hardcode it in your codebase
  • Monitor delivery — review webhook logs in your dashboard and act on failures before the retry window closes