Stripe Payment Fan-Out

This use case demonstrates how to route Stripe payment webhooks to multiple destinations simultaneously—billing API, analytics service, and Slack notifications—each with custom transforms and filters.

Architecture

flowchart LR
    Stripe[Stripe Webhooks] --> Source[Hookbase Source]
    Source --> R1[Route: Billing]
    Source --> R2[Route: Analytics]
    Source --> R3[Route: Slack]
    R1 --> |Transform| D1[Billing API]
    R2 --> |Transform| D2[Analytics Service]
    R3 --> |Transform| D3[Slack Channel]

Flow:

  1. Stripe sends webhooks to Hookbase source endpoint
  2. Source verifies Stripe signature
  3. Three routes evaluate the event in parallel
  4. Each route applies filters and transforms
  5. Transformed events delivered to respective destinations

Step 1: Create the Source

Create a Stripe source with signature verification:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe Production",
    "slug": "stripe-prod",
    "description": "Production Stripe webhooks",
    "verificationConfig": {
      "type": "stripe",
      "secret": "whsec_your_stripe_webhook_signing_secret"
    }
  }'

Response:

{
  "data": {
    "id": "src_abc123",
    "name": "Stripe Production",
    "slug": "stripe-prod",
    "url": "https://api.hookbase.app/ingest/your-org/stripe-prod",
    "verificationConfig": {
      "type": "stripe"
    },
    "createdAt": "2026-02-11T10:00:00Z"
  }
}

Tip

Configure this URL in your Stripe Dashboard under Developers → Webhooks → Add endpoint.

Step 2: Create Destinations

Create three destinations for billing, analytics, and Slack:

Billing API Destination

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/destinations \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Billing API",
    "type": "http",
    "config": {
      "url": "https://billing.yourcompany.com/api/webhooks/stripe",
      "method": "POST",
      "headers": {
        "Authorization": "Bearer YOUR_BILLING_API_KEY",
        "Content-Type": "application/json"
      }
    },
    "retryConfig": {
      "maxAttempts": 5,
      "backoffMultiplier": 2,
      "initialInterval": 1000
    }
  }'

Analytics Service Destination

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/destinations \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Analytics Service",
    "type": "http",
    "config": {
      "url": "https://analytics.yourcompany.com/api/events",
      "method": "POST",
      "headers": {
        "X-API-Key": "YOUR_ANALYTICS_API_KEY",
        "Content-Type": "application/json"
      }
    },
    "retryConfig": {
      "maxAttempts": 3,
      "backoffMultiplier": 2,
      "initialInterval": 1000
    }
  }'

Slack Destination

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/destinations \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Slack #payments",
    "type": "slack",
    "config": {
      "url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
    },
    "retryConfig": {
      "maxAttempts": 2,
      "backoffMultiplier": 1.5,
      "initialInterval": 500
    }
  }'

Step 3: Create Routes with Filters

Billing Route

Route critical payment events to billing API:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe → Billing",
    "sourceId": "src_abc123",
    "destinationId": "dst_billing123",
    "filterId": "flt_billing123",
    "transformId": "tfm_billing123",
    "enabled": true
  }'

Filter for critical payment events:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Critical Payment Events",
    "conditions": [
      {
        "field": "type",
        "operator": "in",
        "value": ["checkout.session.completed", "invoice.paid", "invoice.payment_succeeded", "payment_intent.succeeded"]
      }
    ],
    "logic": "AND"
  }'

Analytics Route

Route all payment-related events to analytics:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe → Analytics",
    "sourceId": "src_abc123",
    "destinationId": "dst_analytics123",
    "filterId": "flt_analytics123",
    "transformId": "tfm_analytics123",
    "enabled": true
  }'

Filter for all payment events:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "All Payment Events",
    "conditions": [
      {
        "field": "type",
        "operator": "matches",
        "value": "^(charge\\.|invoice\\.|payment_intent\\.|checkout\\.session\\.)"
      }
    ],
    "logic": "AND"
  }'

Slack Route

Route failed payments and high-value transactions to Slack:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe → Slack Alerts",
    "sourceId": "src_abc123",
    "destinationId": "dst_slack123",
    "filterId": "flt_slack123",
    "transformId": "tfm_slack123",
    "enabled": true
  }'

Filter for failures and high-value events:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Failed or High-Value Payments",
    "conditions": [
      {
        "field": "type",
        "operator": "in",
        "value": ["invoice.payment_failed", "charge.failed", "payment_intent.payment_failed"]
      },
      {
        "field": "data.object.amount",
        "operator": "gte",
        "value": 100000
      }
    ],
    "logic": "OR"
  }'

Tip

The amount is in cents. 100000 = $1,000 USD. Adjust based on your needs.

Step 4: Add Transforms

Billing Transform

Extract essential payment data for billing system:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe → Billing Format",
    "type": "jsonata",
    "config": {
      "expression": "{\n  \"event_type\": type,\n  \"event_id\": id,\n  \"customer_id\": data.object.customer,\n  \"subscription_id\": data.object.subscription,\n  \"amount\": data.object.amount_total ? data.object.amount_total : data.object.amount,\n  \"currency\": data.object.currency,\n  \"status\": data.object.status,\n  \"payment_method\": data.object.payment_method_types ? data.object.payment_method_types[0] : data.object.payment_method,\n  \"invoice_id\": data.object.invoice,\n  \"metadata\": data.object.metadata,\n  \"timestamp\": created * 1000\n}"
    }
  }'

Example Output:

{
  "event_type": "checkout.session.completed",
  "event_id": "evt_1234567890",
  "customer_id": "cus_abc123",
  "subscription_id": "sub_xyz789",
  "amount": 4999,
  "currency": "usd",
  "status": "complete",
  "payment_method": "card",
  "invoice_id": "in_def456",
  "metadata": {
    "plan": "pro",
    "annual": "true"
  },
  "timestamp": 1707649200000
}

Analytics Transform

Reshape into analytics event format:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe → Analytics Format",
    "type": "jsonata",
    "config": {
      "expression": "{\n  \"event_name\": \"payment_\" & $substringAfter(type, \".\"),\n  \"timestamp\": created * 1000,\n  \"user_id\": data.object.customer,\n  \"properties\": {\n    \"amount\": data.object.amount_total ? data.object.amount_total : data.object.amount,\n    \"currency\": data.object.currency,\n    \"status\": data.object.status,\n    \"payment_method\": data.object.payment_method_types ? data.object.payment_method_types[0] : data.object.payment_method,\n    \"subscription_id\": data.object.subscription,\n    \"invoice_id\": data.object.invoice,\n    \"stripe_event_type\": type,\n    \"stripe_event_id\": id\n  },\n  \"context\": {\n    \"source\": \"stripe\",\n    \"version\": api_version\n  }\n}"
    }
  }'

Example Output:

{
  "event_name": "payment_session_completed",
  "timestamp": 1707649200000,
  "user_id": "cus_abc123",
  "properties": {
    "amount": 4999,
    "currency": "usd",
    "status": "complete",
    "payment_method": "card",
    "subscription_id": "sub_xyz789",
    "invoice_id": "in_def456",
    "stripe_event_type": "checkout.session.completed",
    "stripe_event_id": "evt_1234567890"
  },
  "context": {
    "source": "stripe",
    "version": "2024-11-20.acacia"
  }
}

Slack Transform

Format as rich Slack Block Kit message:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe → Slack Blocks",
    "type": "jsonata",
    "config": {
      "expression": "(\n  $amount := data.object.amount_total ? data.object.amount_total : data.object.amount;\n  $currency := $uppercase(data.object.currency);\n  $isFailed := $contains(type, \"failed\");\n  $color := $isFailed ? \"#ff0000\" : ($amount >= 100000 ? \"#00ff00\" : \"#cccccc\");\n  $emoji := $isFailed ? \":x:\" : \":moneybag:\";\n  {\n    \"attachments\": [\n      {\n        \"color\": $color,\n        \"blocks\": [\n          {\n            \"type\": \"header\",\n            \"text\": {\n              \"type\": \"plain_text\",\n              \"text\": $emoji & \" \" & ($isFailed ? \"Payment Failed\" : \"Payment Received\")\n            }\n          },\n          {\n            \"type\": \"section\",\n            \"fields\": [\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Amount:*\\n\" & $currency & \" \" & $string($amount / 100)\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Customer:*\\n\" & data.object.customer\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Status:*\\n\" & data.object.status\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Event:*\\n\" & type\n              }\n            ]\n          },\n          {\n            \"type\": \"context\",\n            \"elements\": [\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"Event ID: \" & id & \" | <https://dashboard.stripe.com/events/\" & id & \"|View in Stripe>\"\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  }\n)"
    }
  }'

Example Output (Slack Message):

{
  "attachments": [
    {
      "color": "#00ff00",
      "blocks": [
        {
          "type": "header",
          "text": {
            "type": "plain_text",
            "text": ":moneybag: Payment Received"
          }
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Amount:*\nUSD 49.99"
            },
            {
              "type": "mrkdwn",
              "text": "*Customer:*\ncus_abc123"
            },
            {
              "type": "mrkdwn",
              "text": "*Status:*\ncomplete"
            },
            {
              "type": "mrkdwn",
              "text": "*Event:*\ncheckout.session.completed"
            }
          ]
        },
        {
          "type": "context",
          "elements": [
            {
              "type": "mrkdwn",
              "text": "Event ID: evt_1234567890 | <https://dashboard.stripe.com/events/evt_1234567890|View in Stripe>"
            }
          ]
        }
      ]
    }
  ]
}

Step 5: Test the Pipeline

Send a test Stripe-like payload to verify routing:

curl -X POST https://api.hookbase.app/ingest/your-org/stripe-prod \
  -H "Content-Type: application/json" \
  -H "Stripe-Signature: t=1234567890,v1=test_signature" \
  -d '{
    "id": "evt_test_123",
    "type": "checkout.session.completed",
    "api_version": "2024-11-20.acacia",
    "created": 1707649200,
    "data": {
      "object": {
        "id": "cs_test_123",
        "customer": "cus_test_abc",
        "subscription": "sub_test_xyz",
        "amount_total": 150000,
        "currency": "usd",
        "status": "complete",
        "payment_method_types": ["card"],
        "invoice": "in_test_def",
        "metadata": {
          "plan": "enterprise",
          "annual": "true"
        }
      }
    }
  }'

Tip

For production testing, use Stripe CLI: stripe trigger checkout.session.completed

Verify Deliveries:

curl https://api.hookbase.app/api/organizations/{orgId}/deliveries?limit=10 \
  -H "Authorization: Bearer YOUR_TOKEN"

You should see 3 deliveries (or 2 if amount filter excluded Slack):

  • Delivery to Billing API with billing format
  • Delivery to Analytics Service with analytics format
  • Delivery to Slack with Block Kit format (if amount >= $1,000 or failed event)

Production Considerations

1. Enable Deduplication

Prevent duplicate event processing if Stripe retries:

curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/sources/src_abc123 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "deduplicationConfig": {
      "enabled": true,
      "keyPath": "id",
      "windowSeconds": 86400
    }
  }'

2. Set Up Failover for Billing Route

Ensure critical payment events reach a backup destination:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/destinations \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Billing API Backup",
    "type": "http",
    "config": {
      "url": "https://billing-backup.yourcompany.com/api/webhooks/stripe",
      "method": "POST",
      "headers": {
        "Authorization": "Bearer YOUR_BACKUP_API_KEY",
        "Content-Type": "application/json"
      }
    }
  }'
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/routes/rte_billing123 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "failoverConfig": {
      "enabled": true,
      "destinationId": "dst_backup123",
      "triggerAfterAttempts": 3
    }
  }'

3. Configure Circuit Breaker

Protect downstream services from overload during incidents:

curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/destinations/dst_billing123 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "circuitBreakerConfig": {
      "enabled": true,
      "failureThreshold": 10,
      "windowSeconds": 60,
      "resetTimeoutSeconds": 300
    }
  }'

4. Set Up Notification Channels

Get alerted when billing deliveries fail:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/notification-channels \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Engineering Alerts",
    "type": "slack",
    "config": {
      "url": "https://hooks.slack.com/services/YOUR/ALERT/URL"
    },
    "events": ["delivery.failed", "destination.circuit_breaker.opened"],
    "filters": {
      "destinationIds": ["dst_billing123"]
    }
  }'

5. Use Scoped API Keys

Create API keys with limited permissions for external services:

curl -X POST https://api.hookbase.app/api/organizations/{orgId}/api-keys \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe Webhook Ingestion",
    "scopes": ["events:create"],
    "sourceIds": ["src_abc123"],
    "expiresAt": "2027-02-11T00:00:00Z"
  }'

Use the returned API key in your Stripe webhook configuration instead of your user token.

See Also