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:
- Stripe sends webhooks to Hookbase source endpoint
- Source verifies Stripe signature
- Three routes evaluate the event in parallel
- Each route applies filters and transforms
- 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.
Related Guides
- Stripe Integration - Provider-specific setup and signature verification
- Transforms - JSONata syntax and advanced examples
- Filters - Complex routing logic and conditions
- Deduplication - Prevent duplicate event processing
- Failover - Ensure critical events are delivered
- Circuit Breaker - Protect downstream services
- Notification Channels - Configure delivery alerts