Multi-Provider Monitoring Dashboard
This use case demonstrates the fan-in pattern—aggregating webhooks from multiple providers (GitHub, Stripe, and Shopify) into a single monitoring dashboard. Each provider's payload is normalized into a common schema using dedicated transforms, giving you a unified view of events across your entire stack.
Architecture
flowchart LR
GH[GitHub Webhooks] --> S1[Source: GitHub]
ST[Stripe Webhooks] --> S2[Source: Stripe]
SH[Shopify Webhooks] --> S3[Source: Shopify]
S1 --> R1[Route: GitHub → Monitor]
S2 --> R2[Route: Stripe → Monitor]
S3 --> R3[Route: Shopify → Monitor]
R1 --> |Transform: Normalize| D1[Monitoring API]
R2 --> |Transform: Normalize| D1
R3 --> |Transform: Normalize| D1Flow:
- Three webhook sources receive events from different providers
- Each source verifies signatures using provider-specific methods
- Routes connect each source to the shared monitoring destination
- Provider-specific transforms normalize payloads into a common schema
- The monitoring API receives a consistent event format regardless of provider
Common Normalized Schema
All provider events are transformed into this unified format:
{
"provider": "github | stripe | shopify",
"event_type": "normalized.event.name",
"severity": "info | warning | error | critical",
"summary": "Human-readable description of the event",
"timestamp": "2026-02-11T14:30:00Z",
"metadata": {
"provider_event_id": "original event ID",
"provider_event_type": "original event type",
"resource_id": "primary resource identifier",
"resource_url": "link to resource in provider dashboard"
}
}This schema enables:
- Unified dashboards across all providers
- Consistent alerting rules regardless of source
- Simplified querying and aggregation
- Easy addition of new providers without changing the dashboard
Step 1: Create Sources
GitHub Source
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub Production",
"slug": "github-monitor",
"description": "GitHub webhooks for monitoring dashboard",
"verificationConfig": {
"type": "github",
"secret": "your_github_webhook_secret"
}
}'Response:
{
"data": {
"id": "src_gh_monitor01",
"name": "GitHub Production",
"slug": "github-monitor",
"url": "https://api.hookbase.app/ingest/your-org/github-monitor",
"verificationConfig": {
"type": "github"
},
"createdAt": "2026-02-11T10:00:00Z"
}
}Stripe Source
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-monitor",
"description": "Stripe webhooks for monitoring dashboard",
"verificationConfig": {
"type": "stripe",
"secret": "whsec_your_stripe_webhook_signing_secret"
}
}'Response:
{
"data": {
"id": "src_st_monitor01",
"name": "Stripe Production",
"slug": "stripe-monitor",
"url": "https://api.hookbase.app/ingest/your-org/stripe-monitor",
"verificationConfig": {
"type": "stripe"
},
"createdAt": "2026-02-11T10:00:00Z"
}
}Shopify Source
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Shopify Production",
"slug": "shopify-monitor",
"description": "Shopify webhooks for monitoring dashboard",
"verificationConfig": {
"type": "shopify",
"secret": "your_shopify_webhook_signing_secret"
}
}'Response:
{
"data": {
"id": "src_sh_monitor01",
"name": "Shopify Production",
"slug": "shopify-monitor",
"url": "https://api.hookbase.app/ingest/your-org/shopify-monitor",
"verificationConfig": {
"type": "shopify"
},
"createdAt": "2026-02-11T10:00:00Z"
}
}TIP
Configure each source URL in the respective provider's webhook settings. You can use the same Hookbase organization for all providers—each source has a unique slug and endpoint.
Step 2: Create the Shared Destination
Create a single monitoring API destination that receives all normalized events:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/destinations \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Monitoring Dashboard API",
"type": "http",
"config": {
"url": "https://monitoring.yourcompany.com/api/events/ingest",
"method": "POST",
"headers": {
"Authorization": "Bearer YOUR_MONITORING_API_KEY",
"Content-Type": "application/json",
"X-Source": "hookbase"
}
},
"retryConfig": {
"maxAttempts": 5,
"backoffMultiplier": 2,
"initialInterval": 1000
}
}'Response:
{
"data": {
"id": "dst_monitor01",
"name": "Monitoring Dashboard API",
"type": "http",
"createdAt": "2026-02-11T10:00:00Z"
}
}Step 3: Create Transforms
Each transform normalizes a provider's payload into the common schema.
GitHub Transform
Maps GitHub events (push, pull_request, deployment, issues) to the normalized schema:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub → Normalized Monitor",
"type": "jsonata",
"config": {
"expression": "(\n $event := headers.\"x-github-event\";\n $severity := $event = \"security_advisory\" ? \"critical\" : ($event in [\"deployment_status\", \"workflow_run\"] ? (deployment_status.state = \"failure\" or workflow_run.conclusion = \"failure\" ? \"error\" : \"info\") : ($event = \"pull_request\" ? \"info\" : \"info\"));\n $summary := $event = \"push\" ? sender.login & \" pushed \" & $string($count(commits)) & \" commit(s) to \" & repository.full_name & \"/\" & $substringAfter(ref, \"refs/heads/\") : ($event = \"pull_request\" ? sender.login & \" \" & action & \" PR #\" & $string(pull_request.number) & \": \" & pull_request.title : ($event = \"deployment_status\" ? \"Deployment to \" & deployment.environment & \" \" & deployment_status.state : ($event = \"issues\" ? sender.login & \" \" & action & \" issue #\" & $string(issue.number) & \": \" & issue.title : $event & \" event from \" & repository.full_name)));\n $resourceId := $event = \"push\" ? after : ($event = \"pull_request\" ? $string(pull_request.id) : ($event = \"deployment_status\" ? $string(deployment.id) : ($event = \"issues\" ? $string(issue.id) : $string(repository.id))));\n $resourceUrl := $event = \"pull_request\" ? pull_request.html_url : ($event = \"issues\" ? issue.html_url : repository.html_url);\n {\n \"provider\": \"github\",\n \"event_type\": \"github.\" & $event & ($exists(action) ? \".\" & action : \"\"),\n \"severity\": $severity,\n \"summary\": $summary,\n \"timestamp\": $now(),\n \"metadata\": {\n \"provider_event_id\": headers.\"x-github-delivery\",\n \"provider_event_type\": $event,\n \"resource_id\": $resourceId,\n \"resource_url\": $resourceUrl,\n \"repository\": repository.full_name,\n \"sender\": sender.login\n }\n }\n)"
}
}'Example Output (push event):
{
"provider": "github",
"event_type": "github.push",
"severity": "info",
"summary": "johndoe pushed 3 commit(s) to company/api/main",
"timestamp": "2026-02-11T14:30:00Z",
"metadata": {
"provider_event_id": "abc123-delivery-id",
"provider_event_type": "push",
"resource_id": "abc1234def5678",
"resource_url": "https://github.com/company/api",
"repository": "company/api",
"sender": "johndoe"
}
}Stripe Transform
Maps Stripe events (payments, subscriptions, invoices) to the normalized schema:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Stripe → Normalized Monitor",
"type": "jsonata",
"config": {
"expression": "(\n $isFailed := $contains(type, \"failed\");\n $isDispute := $contains(type, \"dispute\");\n $severity := $isFailed ? \"error\" : ($isDispute ? \"warning\" : ($contains(type, \"succeeded\") or $contains(type, \"completed\") ? \"info\" : \"info\"));\n $amount := data.object.amount_total ? data.object.amount_total : data.object.amount;\n $currency := $uppercase(data.object.currency);\n $summary := $contains(type, \"payment_intent\") ? \"Payment \" & $substringAfter(type, \"payment_intent.\") & ($amount ? \" - \" & $currency & \" \" & $string($round($amount / 100, 2)) : \"\") : ($contains(type, \"invoice\") ? \"Invoice \" & $substringAfter(type, \"invoice.\") & ($amount ? \" - \" & $currency & \" \" & $string($round($amount / 100, 2)) : \"\") : ($contains(type, \"checkout\") ? \"Checkout session \" & $substringAfter(type, \"checkout.session.\") & ($amount ? \" - \" & $currency & \" \" & $string($round($amount / 100, 2)) : \"\") : ($isDispute ? \"Dispute \" & $substringAfter(type, \"dispute.\") & \" for \" & data.object.reason : type)));\n {\n \"provider\": \"stripe\",\n \"event_type\": \"stripe.\" & $replace(type, \".\", \"_\"),\n \"severity\": $severity,\n \"summary\": $summary,\n \"timestamp\": $fromMillis(created * 1000),\n \"metadata\": {\n \"provider_event_id\": id,\n \"provider_event_type\": type,\n \"resource_id\": data.object.id,\n \"resource_url\": \"https://dashboard.stripe.com/\" & ($contains(type, \"invoice\") ? \"invoices/\" : ($contains(type, \"payment_intent\") ? \"payments/\" : ($contains(type, \"customer\") ? \"customers/\" : \"events/\"))) & (data.object.id ? data.object.id : id),\n \"customer_id\": data.object.customer,\n \"amount\": $amount,\n \"currency\": $currency\n }\n }\n)"
}
}'Example Output (payment_intent.succeeded):
{
"provider": "stripe",
"event_type": "stripe.payment_intent_succeeded",
"severity": "info",
"summary": "Payment succeeded - USD 49.99",
"timestamp": "2026-02-11T14:30:00Z",
"metadata": {
"provider_event_id": "evt_1234567890",
"provider_event_type": "payment_intent.succeeded",
"resource_id": "pi_abc123def456",
"resource_url": "https://dashboard.stripe.com/payments/pi_abc123def456",
"customer_id": "cus_abc123",
"amount": 4999,
"currency": "USD"
}
}Shopify Transform
Maps Shopify events (orders, refunds, inventory) to the normalized schema:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Shopify → Normalized Monitor",
"type": "jsonata",
"config": {
"expression": "(\n $topic := headers.\"x-shopify-topic\";\n $isRefund := $contains($topic, \"refund\");\n $isCancellation := $contains($topic, \"cancelled\");\n $severity := $isRefund ? \"warning\" : ($isCancellation ? \"warning\" : ($contains($topic, \"orders/create\") ? \"info\" : \"info\"));\n $total := total_price ? total_price : (amount ? amount : \"0.00\");\n $summary := $contains($topic, \"orders/create\") ? \"New order #\" & $string(order_number) & \" - $\" & total_price & \" \" & currency : ($contains($topic, \"orders/updated\") ? \"Order #\" & $string(order_number) & \" updated - \" & financial_status : ($isRefund ? \"Refund processed for order #\" & $string(order.order_number) & \" - $\" & $string($sum(refund_line_items.(quantity * $number(line_item.price)))) : ($contains($topic, \"orders/cancelled\") ? \"Order #\" & $string(order_number) & \" cancelled\" : $topic & \" event\")));\n $resourceId := id ? $string(id) : $string(order.id);\n $shopDomain := headers.\"x-shopify-shop-domain\";\n {\n \"provider\": \"shopify\",\n \"event_type\": \"shopify.\" & $replace($topic, \"/\", \"_\"),\n \"severity\": $severity,\n \"summary\": $summary,\n \"timestamp\": created_at ? created_at : $now(),\n \"metadata\": {\n \"provider_event_id\": headers.\"x-shopify-webhook-id\",\n \"provider_event_type\": $topic,\n \"resource_id\": $resourceId,\n \"resource_url\": \"https://\" & $shopDomain & \"/admin/orders/\" & $resourceId,\n \"shop_domain\": $shopDomain,\n \"total_price\": $total,\n \"currency\": currency ? currency : order.currency\n }\n }\n)"
}
}'Example Output (orders/create):
{
"provider": "shopify",
"event_type": "shopify.orders_create",
"severity": "info",
"summary": "New order #1042 - $749.95 USD",
"timestamp": "2026-02-11T14:30:00-05:00",
"metadata": {
"provider_event_id": "b5678-webhook-id",
"provider_event_type": "orders/create",
"resource_id": "5678901234",
"resource_url": "https://your-store.myshopify.com/admin/orders/5678901234",
"shop_domain": "your-store.myshopify.com",
"total_price": "749.95",
"currency": "USD"
}
}Step 4: Create Routes
Connect each source to the shared monitoring destination with its provider-specific transform.
GitHub → Monitor
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub → Monitor Dashboard",
"sourceId": "src_gh_monitor01",
"destinationId": "dst_monitor01",
"transformId": "tfm_gh_normalize01",
"enabled": true
}'Stripe → Monitor
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Stripe → Monitor Dashboard",
"sourceId": "src_st_monitor01",
"destinationId": "dst_monitor01",
"transformId": "tfm_st_normalize01",
"enabled": true
}'Shopify → Monitor
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Shopify → Monitor Dashboard",
"sourceId": "src_sh_monitor01",
"destinationId": "dst_monitor01",
"transformId": "tfm_sh_normalize01",
"enabled": true
}'Step 5: Test the Pipeline
Send test payloads from each provider to verify normalization.
Test GitHub Event
curl -X POST https://api.hookbase.app/ingest/your-org/github-monitor \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: push" \
-H "X-Hub-Signature-256: sha256=test_signature" \
-H "X-GitHub-Delivery: test-delivery-gh-001" \
-d '{
"ref": "refs/heads/main",
"after": "abc1234def5678",
"commits": [
{"id": "abc1234", "message": "Fix payment flow"},
{"id": "def5678", "message": "Update tests"},
{"id": "ghi9012", "message": "Bump version"}
],
"sender": {
"login": "johndoe"
},
"repository": {
"full_name": "company/api",
"html_url": "https://github.com/company/api"
}
}'Test Stripe Event
curl -X POST https://api.hookbase.app/ingest/your-org/stripe-monitor \
-H "Content-Type: application/json" \
-H "Stripe-Signature: t=1234567890,v1=test_signature" \
-d '{
"id": "evt_test_stripe_001",
"type": "payment_intent.succeeded",
"created": 1707649200,
"data": {
"object": {
"id": "pi_abc123def456",
"amount": 4999,
"currency": "usd",
"customer": "cus_abc123",
"status": "succeeded"
}
}
}'Test Shopify Event
curl -X POST https://api.hookbase.app/ingest/your-org/shopify-monitor \
-H "Content-Type: application/json" \
-H "X-Shopify-Topic: orders/create" \
-H "X-Shopify-Hmac-Sha256: test_signature" \
-H "X-Shopify-Shop-Domain: your-store.myshopify.com" \
-H "X-Shopify-Webhook-Id: test-webhook-sh-001" \
-d '{
"id": 5678901234,
"order_number": 1042,
"total_price": "749.95",
"currency": "USD",
"financial_status": "paid",
"customer": {
"id": 7890123456,
"first_name": "Jane",
"last_name": "Smith",
"email": "[email protected]"
},
"line_items": [
{
"title": "Widget Pro",
"quantity": 2,
"price": "299.99"
}
],
"created_at": "2026-02-11T14:30:00-05:00"
}'Verify Deliveries:
curl https://api.hookbase.app/api/organizations/{orgId}/deliveries?limit=10 \
-H "Authorization: Bearer YOUR_TOKEN"You should see 3 deliveries to the monitoring destination, each with a normalized payload matching the common schema.
Adding a New Provider
The fan-in pattern makes it straightforward to add new providers. For example, to add Twilio:
- Create a source with Twilio signature verification
- Create a transform that maps Twilio payloads to the common schema
- Create a route connecting the source to the existing monitoring destination
No changes needed to the destination or dashboard—the normalized schema handles it.
# 1. Source
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Twilio Production",
"slug": "twilio-monitor",
"verificationConfig": {
"type": "twilio",
"secret": "your_twilio_auth_token"
}
}'
# 2. Transform
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Twilio → Normalized Monitor",
"type": "jsonata",
"config": {
"expression": "{\n \"provider\": \"twilio\",\n \"event_type\": \"twilio.\" & EventType,\n \"severity\": ErrorCode ? \"error\" : \"info\",\n \"summary\": \"SMS \" & MessageStatus & \" - \" & To,\n \"timestamp\": $now(),\n \"metadata\": {\n \"provider_event_id\": MessageSid,\n \"provider_event_type\": EventType,\n \"resource_id\": MessageSid,\n \"resource_url\": \"https://console.twilio.com/develop/sms/logs/\" & MessageSid\n }\n}"
}
}'
# 3. Route
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Twilio → Monitor Dashboard",
"sourceId": "src_tw_monitor01",
"destinationId": "dst_monitor01",
"transformId": "tfm_tw_normalize01",
"enabled": true
}'Production Considerations
1. Enable Deduplication on All Sources
Prevent duplicate events from any provider:
# Apply to each source
for SOURCE_ID in src_gh_monitor01 src_st_monitor01 src_sh_monitor01; do
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/sources/$SOURCE_ID \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"deduplicationConfig": {
"enabled": true,
"keyPath": "id",
"windowSeconds": 86400
}
}'
done2. Configure Circuit Breaker for Monitoring
Protect your monitoring API if it becomes overwhelmed:
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/destinations/dst_monitor01 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"circuitBreakerConfig": {
"enabled": true,
"failureThreshold": 15,
"windowSeconds": 60,
"resetTimeoutSeconds": 120
}
}'3. Set Up Delivery Failure Alerts
Get notified when monitoring events fail to deliver:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/notification-channels \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Monitor Pipeline Alerts",
"type": "slack",
"config": {
"url": "https://hooks.slack.com/services/YOUR/ALERT/URL"
},
"events": ["delivery.failed", "destination.circuit_breaker.opened"],
"filters": {
"destinationIds": ["dst_monitor01"]
}
}'Related Guides
- GitHub Integration - GitHub signature verification and event types
- Stripe Integration - Stripe webhook setup and events
- Shopify Integration - Shopify HMAC verification and topics
- Twilio Integration - Twilio signature verification
- Transforms - JSONata syntax and advanced examples
- Pipeline Architecture - Understanding the full data flow
- Circuit Breaker - Protect downstream services
- Deduplication - Prevent duplicate event processing