Skip to content

Webhooks

Webhooks notify your application in real time when events happen in SubscribeFlow. Instead of polling the API, you register an endpoint URL and SubscribeFlow sends an HTTP POST request for every matching event.

Create a webhook endpoint

webhook = await client.webhooks.create(
    url="https://your-app.com/webhooks/subscribeflow",
    events=["subscriber.created", "tag.subscribed"],
    description="Main webhook endpoint",
)
# Store the signing secret securely!
print(f"Secret: {webhook.secret}")
const webhook = await client.webhooks.create({
  url: 'https://your-app.com/webhooks/subscribeflow',
  events: ['subscriber.created', 'tag.subscribed'],
  description: 'Main webhook endpoint',
});

// Store the signing secret securely!
console.log('Secret:', webhook.signing_secret);
curl -X POST https://api.subscribeflow.net/api/v1/webhooks \
  -H "X-API-Key: sf_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/subscribeflow",
    "events": ["subscriber.created", "tag.subscribed"],
    "description": "Main webhook endpoint"
  }'

Warning

The signing secret is only returned when the webhook is created. Store it in a secret manager or environment variable immediately.

Event types

Event Description
subscriber.created A new subscriber was added
subscriber.updated Subscriber data (email, status, metadata) changed
subscriber.deleted A subscriber was permanently deleted
tag.subscribed A subscriber was added to a tag
tag.unsubscribed A subscriber was removed from a tag
subscriber.note_added A note was added to a subscriber

Each webhook delivery includes a JSON payload with the event type, a timestamp, and the full resource object.

Signature verification

Every delivery includes an X-SubscribeFlow-Signature header containing an HMAC-SHA256 signature of the request body. Always verify this signature before processing the payload.

import hmac
import hashlib

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

# In your webhook handler (FastAPI example):
from fastapi import Request, HTTPException

@app.post("/webhooks/subscribeflow")
async def handle_webhook(request: Request):
    body = await request.body()
    signature = request.headers.get("X-SubscribeFlow-Signature", "")

    if not verify_signature(body, signature, WEBHOOK_SECRET):
        raise HTTPException(status_code=401, detail="Invalid signature")

    event = await request.json()
    print(f"Received: {event['event_type']}")
import crypto from 'node:crypto';

function verifySignature(
  payload: string,
  signature: string,
  secret: string,
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return signature === `sha256=${expected}`;
}

// In your webhook handler (Express example):
import express from 'express';

const app = express();
app.use(express.raw({ type: 'application/json' }));

app.post('/webhooks/subscribeflow', (req, res) => {
  const signature = req.headers['x-subscribeflow-signature'] as string;
  const body = req.body.toString();

  if (!verifySignature(body, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(body);
  console.log('Received:', event.event_type);
  res.sendStatus(200);
});
# Verify a signature manually with openssl:
PAYLOAD='{"event_type":"subscriber.created","data":{...}}'
SECRET="whsec_..."

EXPECTED=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
echo "sha256=$EXPECTED"

Warning

Always use a constant-time comparison function (like hmac.compare_digest in Python or crypto.timingSafeEqual in Node.js) to prevent timing attacks.

Delivery monitoring

List deliveries

deliveries = await client.webhooks.list_deliveries("webhook-id")
for d in deliveries:
    print(f"{d.event_type}: {d.status}")
const deliveries = await client.webhooks.listDeliveries('webhook-id');
for (const d of deliveries.items) {
  console.log(`${d.event_type}: ${d.status}`);
}
curl https://api.subscribeflow.net/api/v1/webhooks/WEBHOOK_ID/deliveries \
  -H "X-API-Key: sf_live_..."

Retry a failed delivery

await client.webhooks.retry_delivery("webhook-id", "delivery-id")
await client.webhooks.retryDelivery('webhook-id', 'delivery-id');
curl -X POST \
  https://api.subscribeflow.net/api/v1/webhooks/WEBHOOK_ID/deliveries/DELIVERY_ID/retry \
  -H "X-API-Key: sf_live_..."

Test a webhook

Send a test event to verify your endpoint is reachable and correctly configured.

result = await client.webhooks.test("webhook-id")
if result.success:
    print(f"Working! Response time: {result.response_time_ms}ms")
else:
    print(f"Failed: {result.error}")
const result = await client.webhooks.test('webhook-id', 'subscriber.created');
if (result.success) {
  console.log(`Working! Response time: ${result.response_time_ms}ms`);
} else {
  console.log('Failed:', result.error);
}
curl -X POST https://api.subscribeflow.net/api/v1/webhooks/WEBHOOK_ID/test \
  -H "X-API-Key: sf_live_..."

Hands-On: React to subscriber events

Set up a webhook that logs every new subscriber to your application.

import asyncio
from subscribeflow import SubscribeFlowClient

async def main():
    async with SubscribeFlowClient(api_key="sf_live_...") as client:
        # 1. Create a webhook for new subscribers
        webhook = await client.webhooks.create(
            url="https://your-app.com/webhooks/subscribeflow",
            events=[
                "subscriber.created",
                "subscriber.deleted",
                "tag.subscribed",
                "tag.unsubscribed",
            ],
            description="Track all subscriber activity",
        )
        print(f"Webhook created: {webhook.id}")
        print(f"Secret: {webhook.secret}")

        # 2. Test the endpoint
        result = await client.webhooks.test(webhook.id)
        if result.success:
            print("Endpoint verified!")
        else:
            print(f"Test failed: {result.error}")

asyncio.run(main())
import { SubscribeFlowClient } from '@subscribeflow/sdk';

const client = new SubscribeFlowClient({
  apiKey: 'sf_live_...',
});

// 1. Create a webhook for new subscribers
const webhook = await client.webhooks.create({
  url: 'https://your-app.com/webhooks/subscribeflow',
  events: [
    'subscriber.created',
    'subscriber.deleted',
    'tag.subscribed',
    'tag.unsubscribed',
  ],
  description: 'Track all subscriber activity',
});

console.log('Webhook created:', webhook.id);
console.log('Secret:', webhook.signing_secret);

// 2. Test the endpoint
const result = await client.webhooks.test(webhook.id, 'subscriber.created');
if (result.success) {
  console.log('Endpoint verified!');
} else {
  console.log('Test failed:', result.error);
}
# 1. Create a webhook (save the secret from the response!)
curl -X POST https://api.subscribeflow.net/api/v1/webhooks \
  -H "X-API-Key: sf_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/subscribeflow",
    "events": ["subscriber.created", "subscriber.deleted", "tag.subscribed", "tag.unsubscribed"],
    "description": "Track all subscriber activity"
  }'

# 2. Test the endpoint
curl -X POST https://api.subscribeflow.net/api/v1/webhooks/WEBHOOK_ID/test \
  -H "X-API-Key: sf_live_..."