Multi-Payment Processor Consolidation
Consolidate transaction webhooks from Stripe, Paddle, and Square into a unified format. Events are normalized and routed to a central finance system, with high-value transactions triggering Slack alerts. This fan-in pattern with content-based routing is essential for businesses accepting payments through multiple processors.
Architecture
flowchart LR
Stripe[Stripe Webhooks] --> S1[Source: Stripe]
Paddle[Paddle Webhooks] --> S2[Source: Paddle]
Square[Square Webhooks] --> S3[Source: Square]
S1 --> R1[Route: Stripe → Finance]
S1 --> R2[Route: Stripe → High-Value Alerts]
S2 --> R3[Route: Paddle → Finance]
S2 --> R4[Route: Paddle → High-Value Alerts]
S3 --> R5[Route: Square → Finance]
S3 --> R6[Route: Square → High-Value Alerts]
R1 --> |Transform| D1[Finance API]
R3 --> |Transform| D1
R5 --> |Transform| D1
R2 --> |Transform| D2[Slack #high-value]
R4 --> |Transform| D2
R6 --> |Transform| D2Flow:
- Three payment processors send webhooks to their respective Hookbase source endpoints
- Each source verifies the provider-specific signature
- Routes evaluate events using content-based filters (event type, amount thresholds)
- Transforms normalize each provider's payload into a unified transaction schema
- Finance system receives consistent records; Slack receives high-value alerts
Key challenge: Stripe uses amounts in cents, Paddle reports totals with tax included as a Merchant of Record (MoR), and Square nests amounts inside a money object. The transforms normalize all of this.
Step 1: Create Sources
Create one source per payment processor with signature verification.
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-payments",
"description": "Stripe payment webhooks for consolidation",
"verificationConfig": {
"type": "stripe",
"secret": "whsec_your_stripe_webhook_signing_secret"
}
}'Response:
{
"data": {
"id": "src_stripe01",
"name": "Stripe Production",
"slug": "stripe-payments",
"url": "https://api.hookbase.app/ingest/your-org/stripe-payments",
"verificationConfig": {
"type": "stripe"
},
"createdAt": "2026-03-01T10:00:00Z"
}
}Paddle Source
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Paddle Production",
"slug": "paddle-payments",
"description": "Paddle payment webhooks for consolidation",
"verificationConfig": {
"type": "hmac",
"secret": "pdl_your_paddle_webhook_secret",
"algorithm": "sha256",
"header": "Paddle-Signature",
"encoding": "hex"
}
}'Response:
{
"data": {
"id": "src_paddle01",
"name": "Paddle Production",
"slug": "paddle-payments",
"url": "https://api.hookbase.app/ingest/your-org/paddle-payments",
"verificationConfig": {
"type": "hmac"
},
"createdAt": "2026-03-01T10:01:00Z"
}
}TIP
Paddle is a Merchant of Record (MoR). Its payloads include tax, fees, and payout details that differ from direct processors. The transforms in Step 4 account for this.
Square Source
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Square Production",
"slug": "square-payments",
"description": "Square payment webhooks for consolidation",
"verificationConfig": {
"type": "hmac",
"secret": "your_square_webhook_signature_key",
"algorithm": "sha1",
"header": "X-Square-Hmacsha256-Signature",
"encoding": "base64"
}
}'Response:
{
"data": {
"id": "src_square01",
"name": "Square Production",
"slug": "square-payments",
"url": "https://api.hookbase.app/ingest/your-org/square-payments",
"verificationConfig": {
"type": "hmac"
},
"createdAt": "2026-03-01T10:02:00Z"
}
}Step 2: Create Destinations
Finance 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": "Finance API",
"type": "http",
"config": {
"url": "https://finance.yourcompany.com/api/transactions",
"method": "POST",
"headers": {
"Authorization": "Bearer YOUR_FINANCE_API_KEY",
"Content-Type": "application/json"
}
},
"retryConfig": {
"maxAttempts": 5,
"backoffMultiplier": 2,
"initialInterval": 1000
}
}'Slack High-Value Alerts 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 #high-value-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
Each source needs two routes: one to the finance system (successful transactions) and one to Slack (high-value only). Filters use event types and amount thresholds for content-based routing.
Stripe Routes
Filter for successful Stripe payments:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Stripe Successful Payments",
"conditions": [
{
"field": "type",
"operator": "in",
"value": ["checkout.session.completed", "payment_intent.succeeded", "invoice.paid", "charge.succeeded"]
}
],
"logic": "AND"
}'Route: Stripe to Finance:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Stripe → Finance",
"sourceId": "src_stripe01",
"destinationId": "dst_finance01",
"filterId": "flt_strfin01",
"transformId": "tfm_strfin01",
"enabled": true
}'Filter for high-value Stripe payments ($500+):
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Stripe High-Value Payments",
"conditions": [
{
"field": "type",
"operator": "in",
"value": ["checkout.session.completed", "payment_intent.succeeded", "charge.succeeded"]
},
{
"field": "data.object.amount",
"operator": "gte",
"value": 50000
}
],
"logic": "AND"
}'TIP
Stripe amounts are in the smallest currency unit (cents for USD). 50000 = $500.00. Adjust the threshold based on your transaction volume and what you consider "high-value."
Route: Stripe to Slack Alerts:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Stripe → High-Value Alerts",
"sourceId": "src_stripe01",
"destinationId": "dst_slack01",
"filterId": "flt_strhv01",
"transformId": "tfm_strhv01",
"enabled": true
}'Paddle Routes
Filter for successful Paddle transactions:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Paddle Successful Transactions",
"conditions": [
{
"field": "event_type",
"operator": "in",
"value": ["transaction.completed", "transaction.paid"]
}
],
"logic": "AND"
}'Route: Paddle to Finance:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Paddle → Finance",
"sourceId": "src_paddle01",
"destinationId": "dst_finance01",
"filterId": "flt_pdlfin01",
"transformId": "tfm_pdlfin01",
"enabled": true
}'Filter for high-value Paddle transactions ($500+):
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Paddle High-Value Transactions",
"conditions": [
{
"field": "event_type",
"operator": "in",
"value": ["transaction.completed", "transaction.paid"]
},
{
"field": "data.details.totals.grand_total",
"operator": "gte",
"value": "500.00"
}
],
"logic": "AND"
}'Route: Paddle to Slack Alerts:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Paddle → High-Value Alerts",
"sourceId": "src_paddle01",
"destinationId": "dst_slack01",
"filterId": "flt_pdlhv01",
"transformId": "tfm_pdlhv01",
"enabled": true
}'Square Routes
Filter for successful Square payments:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Square Successful Payments",
"conditions": [
{
"field": "type",
"operator": "in",
"value": ["payment.completed", "payment.updated"]
},
{
"field": "data.object.payment.status",
"operator": "eq",
"value": "COMPLETED"
}
],
"logic": "AND"
}'Route: Square to Finance:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Square → Finance",
"sourceId": "src_square01",
"destinationId": "dst_finance01",
"filterId": "flt_sqrfin01",
"transformId": "tfm_sqrfin01",
"enabled": true
}'Filter for high-value Square payments ($500+):
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Square High-Value Payments",
"conditions": [
{
"field": "type",
"operator": "in",
"value": ["payment.completed", "payment.updated"]
},
{
"field": "data.object.payment.status",
"operator": "eq",
"value": "COMPLETED"
},
{
"field": "data.object.payment.amount_money.amount",
"operator": "gte",
"value": 50000
}
],
"logic": "AND"
}'TIP
Square amounts are also in the smallest currency unit (cents for USD), matching Stripe's convention. The filter threshold of 50000 equals $500.00.
Route: Square to Slack Alerts:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Square → High-Value Alerts",
"sourceId": "src_square01",
"destinationId": "dst_slack01",
"filterId": "flt_sqrhv01",
"transformId": "tfm_sqrhv01",
"enabled": true
}'Step 4: Add Transforms
Each payment processor uses a different payload structure. The transforms normalize everything into a unified transaction schema that the finance system expects.
All three finance transforms output a unified schema with consistent fields: transaction_id, provider, amount_cents, currency, tax_cents, net_amount_cents, fee_cents, is_merchant_of_record, and timestamp. This lets your finance system process transactions identically regardless of origin.
Stripe Finance Transform
Normalize Stripe payment events into the unified 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 → Unified Transaction",
"type": "jsonata",
"config": {
"expression": "(\n $amount := data.object.amount_total ? data.object.amount_total : data.object.amount;\n $currency := $uppercase(data.object.currency);\n {\n \"transaction_id\": \"str_\" & data.object.id,\n \"provider\": \"stripe\",\n \"provider_event_id\": id,\n \"customer_id\": data.object.customer,\n \"amount_cents\": $amount,\n \"currency\": $currency,\n \"amount_display\": $string($amount / 100),\n \"tax_cents\": data.object.total_details.amount_tax ? data.object.total_details.amount_tax : 0,\n \"net_amount_cents\": $amount - (data.object.total_details.amount_tax ? data.object.total_details.amount_tax : 0),\n \"fee_cents\": 0,\n \"is_merchant_of_record\": false,\n \"payment_method\": data.object.payment_method_types ? data.object.payment_method_types[0] : \"card\",\n \"status\": \"succeeded\",\n \"provider_event_type\": type,\n \"metadata\": data.object.metadata ? data.object.metadata : {},\n \"timestamp\": $fromMillis(created * 1000)\n }\n)"
}
}'Example Output (from a Stripe checkout.session.completed):
{
"transaction_id": "str_cs_abc123",
"provider": "stripe",
"provider_event_id": "evt_1234567890",
"customer_id": "cus_abc123",
"amount_cents": 14999,
"currency": "USD",
"amount_display": "149.99",
"tax_cents": 1200,
"net_amount_cents": 13799,
"fee_cents": 0,
"is_merchant_of_record": false,
"payment_method": "card",
"status": "succeeded",
"provider_event_type": "checkout.session.completed",
"metadata": {
"plan": "pro",
"annual": "true"
},
"timestamp": "2026-03-01T10:00:00.000Z"
}Paddle Finance Transform
Paddle is a Merchant of Record -- it collects payments, handles taxes, and remits a payout. The transform extracts net earnings alongside the gross total:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Paddle → Unified Transaction",
"type": "jsonata",
"config": {
"expression": "(\n $total := $number(data.details.totals.grand_total);\n $tax := $number(data.details.totals.tax);\n $fee := $number(data.details.totals.fee ? data.details.totals.fee : \"0\");\n $earnings := $number(data.details.totals.earnings ? data.details.totals.earnings : $string($total - $tax - $fee));\n $currency := $uppercase(data.currency_code);\n {\n \"transaction_id\": \"pdl_\" & data.id,\n \"provider\": \"paddle\",\n \"provider_event_id\": event_id,\n \"customer_id\": data.customer_id,\n \"amount_cents\": $round($total * 100),\n \"currency\": $currency,\n \"amount_display\": $string($total),\n \"tax_cents\": $round($tax * 100),\n \"net_amount_cents\": $round($earnings * 100),\n \"fee_cents\": $round($fee * 100),\n \"is_merchant_of_record\": true,\n \"payment_method\": data.payments[0].method_details.type ? data.payments[0].method_details.type : \"unknown\",\n \"status\": \"succeeded\",\n \"provider_event_type\": event_type,\n \"metadata\": data.custom_data ? data.custom_data : {},\n \"timestamp\": occurred_at\n }\n)"
}
}'WARNING
Paddle reports amounts as decimal strings (e.g., "149.99") rather than cents. The transform converts using $round($total * 100) to avoid floating-point issues. The is_merchant_of_record: true flag lets your finance system distinguish MoR transactions.
Example Output (from a Paddle transaction.completed):
{
"transaction_id": "pdl_txn_01h8bxpvx2T9XYZ",
"provider": "paddle",
"provider_event_id": "ntf_01h8bxqyz3ABC",
"customer_id": "ctm_01h7mrr5j8DEF",
"amount_cents": 14999,
"currency": "USD",
"amount_display": "149.99",
"tax_cents": 2400,
"net_amount_cents": 11249,
"fee_cents": 1350,
"is_merchant_of_record": true,
"payment_method": "card",
"status": "succeeded",
"provider_event_type": "transaction.completed",
"metadata": {
"plan": "pro",
"source": "website"
},
"timestamp": "2026-03-01T10:05:00.000000Z"
}Square Finance Transform
Square nests amounts inside a money object with separate amount and currency fields:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Square → Unified Transaction",
"type": "jsonata",
"config": {
"expression": "(\n $payment := data.object.payment;\n $amount := $payment.amount_money.amount;\n $currency := $uppercase($payment.amount_money.currency);\n $tip := $payment.tip_money.amount ? $payment.tip_money.amount : 0;\n $fee := $payment.processing_fee ? $sum($payment.processing_fee.amount_money.amount) : 0;\n {\n \"transaction_id\": \"sqr_\" & $payment.id,\n \"provider\": \"square\",\n \"provider_event_id\": event_id ? event_id : $payment.id,\n \"customer_id\": $payment.customer_id ? $payment.customer_id : \"anonymous\",\n \"amount_cents\": $amount,\n \"currency\": $currency,\n \"amount_display\": $string($amount / 100),\n \"tax_cents\": $payment.tax_money.amount ? $payment.tax_money.amount : 0,\n \"net_amount_cents\": $amount - $fee,\n \"fee_cents\": $fee,\n \"is_merchant_of_record\": false,\n \"payment_method\": $payment.source_type ? $lowercase($payment.source_type) : \"card\",\n \"status\": \"succeeded\",\n \"provider_event_type\": type,\n \"metadata\": {\n \"location_id\": $payment.location_id,\n \"tip_cents\": $tip\n },\n \"timestamp\": $payment.created_at\n }\n)"
}
}'Example Output (from a Square payment.completed):
{
"transaction_id": "sqr_KkAkhdMYPBJNgD",
"provider": "square",
"provider_event_id": "evt_sq_abc123",
"customer_id": "VDKXEEKPJN48M",
"amount_cents": 15500,
"currency": "USD",
"amount_display": "155",
"tax_cents": 1200,
"net_amount_cents": 15050,
"fee_cents": 450,
"is_merchant_of_record": false,
"payment_method": "card",
"status": "succeeded",
"provider_event_type": "payment.completed",
"metadata": {
"location_id": "LPNKH7GXR3KH8",
"tip_cents": 500
},
"timestamp": "2026-03-01T10:10:00.000Z"
}Slack High-Value Alert Transforms
Each processor needs its own Slack transform to extract the correct amount fields. All produce the same Block Kit structure.
Stripe High-Value Slack Transform:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Stripe → High-Value Slack Alert",
"type": "jsonata",
"config": {
"expression": "(\n $amount := data.object.amount_total ? data.object.amount_total : data.object.amount;\n $currency := $uppercase(data.object.currency);\n {\n \"attachments\": [\n {\n \"color\": \"#635bff\",\n \"blocks\": [\n {\n \"type\": \"header\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \":moneybag: High-Value Payment — Stripe\"\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\": \"*Payment Method:*\\n\" & (data.object.payment_method_types ? data.object.payment_method_types[0] : \"card\")\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\": \"Provider: Stripe | Event: \" & id & \" | <https://dashboard.stripe.com/events/\" & id & \"|View in Stripe>\"\n }\n ]\n }\n ]\n }\n ]\n }\n)"
}
}'Paddle High-Value Slack Transform:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Paddle → High-Value Slack Alert",
"type": "jsonata",
"config": {
"expression": "(\n $total := data.details.totals.grand_total;\n $currency := $uppercase(data.currency_code);\n $earnings := data.details.totals.earnings ? data.details.totals.earnings : $total;\n {\n \"attachments\": [\n {\n \"color\": \"#3b82f6\",\n \"blocks\": [\n {\n \"type\": \"header\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \":moneybag: High-Value Payment — Paddle (MoR)\"\n }\n },\n {\n \"type\": \"section\",\n \"fields\": [\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Gross Amount:*\\n\" & $currency & \" \" & $total\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Net Earnings:*\\n\" & $currency & \" \" & $earnings\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Customer:*\\n\" & data.customer_id\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Tax (incl.):*\\n\" & $currency & \" \" & data.details.totals.tax\n }\n ]\n },\n {\n \"type\": \"context\",\n \"elements\": [\n {\n \"type\": \"mrkdwn\",\n \"text\": \"Provider: Paddle (Merchant of Record) | Transaction: \" & data.id\n }\n ]\n }\n ]\n }\n ]\n }\n)"
}
}'Square High-Value Slack Transform:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Square → High-Value Slack Alert",
"type": "jsonata",
"config": {
"expression": "(\n $payment := data.object.payment;\n $amount := $payment.amount_money.amount;\n $currency := $uppercase($payment.amount_money.currency);\n {\n \"attachments\": [\n {\n \"color\": \"#006aff\",\n \"blocks\": [\n {\n \"type\": \"header\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \":moneybag: High-Value Payment — Square\"\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\" & ($payment.customer_id ? $payment.customer_id : \"Walk-in\")\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Location:*\\n\" & $payment.location_id\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Source:*\\n\" & ($payment.source_type ? $payment.source_type : \"CARD\")\n }\n ]\n },\n {\n \"type\": \"context\",\n \"elements\": [\n {\n \"type\": \"mrkdwn\",\n \"text\": \"Provider: Square | Payment: \" & $payment.id & \" | <https://squareup.com/dashboard/sales/transactions/\" & $payment.id & \"|View in Square>\"\n }\n ]\n }\n ]\n }\n ]\n }\n)"
}
}'Step 5: Test the Pipeline
Send a test payload for each processor to verify routing and transformation.
Test Stripe
curl -X POST https://api.hookbase.app/ingest/your-org/stripe-payments \
-H "Content-Type: application/json" \
-H "Stripe-Signature: t=1234567890,v1=test_signature" \
-d '{
"id": "evt_test_stripe_001",
"type": "checkout.session.completed",
"api_version": "2024-11-20.acacia",
"created": 1709287200,
"data": {
"object": {
"id": "cs_test_stripe_001",
"customer": "cus_test_001",
"subscription": "sub_test_001",
"amount_total": 75000,
"currency": "usd",
"status": "complete",
"payment_method_types": ["card"],
"invoice": "in_test_001",
"total_details": {
"amount_tax": 6000
},
"metadata": {
"plan": "business",
"annual": "true"
}
}
}
}'Test Paddle
curl -X POST https://api.hookbase.app/ingest/your-org/paddle-payments \
-H "Content-Type: application/json" \
-H "Paddle-Signature: ts=1234567890;h1=test_signature" \
-d '{
"event_id": "ntf_test_paddle_001",
"event_type": "transaction.completed",
"occurred_at": "2026-03-01T10:05:00.000000Z",
"data": {
"id": "txn_test_paddle_001",
"customer_id": "ctm_test_001",
"currency_code": "USD",
"details": {
"totals": {
"grand_total": "750.00",
"tax": "120.00",
"fee": "67.50",
"earnings": "562.50"
}
},
"payments": [
{
"method_details": {
"type": "card"
}
}
],
"custom_data": {
"plan": "business",
"source": "website"
}
}
}'Test Square
curl -X POST https://api.hookbase.app/ingest/your-org/square-payments \
-H "Content-Type: application/json" \
-H "X-Square-Hmacsha256-Signature: test_signature" \
-d '{
"event_id": "evt_test_square_001",
"type": "payment.completed",
"data": {
"object": {
"payment": {
"id": "KkAkhdMYtest001",
"customer_id": "VDKXEEKPTEST",
"location_id": "LPNKH7GXTEST",
"amount_money": {
"amount": 82500,
"currency": "USD"
},
"tip_money": {
"amount": 1500
},
"tax_money": {
"amount": 6600
},
"source_type": "CARD",
"status": "COMPLETED",
"created_at": "2026-03-01T10:10:00.000Z"
}
}
}
}'TIP
For production testing, use each provider's test/sandbox mode to generate valid signatures.
Verify Deliveries:
curl https://api.hookbase.app/api/organizations/{orgId}/deliveries?limit=20 \
-H "Authorization: Bearer YOUR_TOKEN"You should see up to 6 deliveries: 3 to Finance API (unified format) and up to 3 to Slack (amounts >= $500 only). Verify that is_merchant_of_record is true only for Paddle transactions.
Production Considerations
1. Enable Deduplication on All Sources
Prevent double-counting when processors retry delivery:
# Stripe — deduplicate on event ID
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/sources/src_stripe01 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"deduplicationConfig": {
"enabled": true,
"keyPath": "id",
"windowSeconds": 86400
}
}'# Paddle — deduplicate on notification ID
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/sources/src_paddle01 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"deduplicationConfig": {
"enabled": true,
"keyPath": "event_id",
"windowSeconds": 86400
}
}'# Square — deduplicate on event ID
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/sources/src_square01 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"deduplicationConfig": {
"enabled": true,
"keyPath": "event_id",
"windowSeconds": 86400
}
}'2. Set Up Failover for Finance Route
Configure a backup destination to catch failed finance deliveries:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/destinations \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Finance API Backup",
"type": "http",
"config": {
"url": "https://finance-backup.yourcompany.com/api/transactions",
"method": "POST",
"headers": {
"Authorization": "Bearer YOUR_BACKUP_FINANCE_KEY",
"Content-Type": "application/json"
}
}
}'Then enable failover on each finance route (repeat for rte_strfin01, rte_pdlfin01, rte_sqrfin01):
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/routes/rte_strfin01 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"failoverConfig": {
"enabled": true,
"destinationId": "dst_finbackup01",
"triggerAfterAttempts": 3
}
}'3. Configure Circuit Breaker
Protect downstream services from webhook bursts:
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/destinations/dst_finance01 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"circuitBreakerConfig": {
"enabled": true,
"failureThreshold": 15,
"windowSeconds": 60,
"resetTimeoutSeconds": 300
}
}'4. Set Up Notification Channels
Get alerted when finance 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": "Finance Pipeline Alerts",
"type": "slack",
"config": {
"url": "https://hooks.slack.com/services/YOUR/ALERT/URL"
},
"events": ["delivery.failed", "destination.circuit_breaker.opened"],
"filters": {
"destinationIds": ["dst_finance01", "dst_finbackup01"]
}
}'5. Currency Normalization
If you receive payments in multiple currencies, include original_amount_cents and original_currency fields in your unified schema (as shown in the transforms above). Your finance system can then apply live exchange rates for exact reconciliation. For approximate reporting, you can add a static rate lookup to the JSONata expression using $lookup() against a rates object like {"EUR": 1.08, "GBP": 1.27, "USD": 1}.
WARNING
Static exchange rates in JSONata expressions are suitable for approximate reporting only. For exact financial reconciliation, call a live exchange rate API in your finance system.
Related Guides
- Stripe Integration - Stripe-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
See Also
- Stripe Integration — Stripe webhook setup
- Paddle Integration — Paddle webhook setup
- Square Integration — Square webhook setup
- Stripe Payment Fan-Out — Related use case