External Webhooks Guide ​
External webhooks allow you to send payment events to your own services, Discord, Slack, analytics platforms, or any HTTP endpoint in real-time.
Overview ​
External webhooks are perfect for:
- Notifications: Send alerts to Discord or Slack
- Analytics: Track payment events in your analytics platform
- Business Logic: Trigger custom workflows in your application
- Monitoring: Monitor payment failures and subscription cancellations
- Integrations: Connect to third-party services
Quick Start ​
1. Enable External Webhooks ​
Add to your environment configuration in Pubflow dashboard:
# Enable the system
EXTERNAL_WEBHOOKS_ENABLED=true
# Optional: Enable debug logs
WEBHOOK_DEBUG_MODE=true2. Configure Your First Webhook ​
# Discord notifications for failed payments
WEBHOOK_1_NAME=discord_alerts
WEBHOOK_1_URL=https://discord.com/api/webhooks/123456789/your-webhook-token
WEBHOOK_1_EVENTS=payment.failed,subscription.cancelled3. Save Configuration ​
Click Save Configuration in Pubflow dashboard. The system will automatically detect and load your webhooks.
Configuration Reference ​
Basic Webhook Configuration ​
Each webhook uses numbered environment variables:
WEBHOOK_1_NAME=my_webhook # Required: Friendly name
WEBHOOK_1_URL=https://example.com # Required: Endpoint URL
WEBHOOK_1_EVENTS=payment.success # Required: Which events to sendComplete Configuration Options ​
WEBHOOK_1_NAME=analytics
WEBHOOK_1_URL=https://analytics.company.com/webhooks
WEBHOOK_1_SECRET=your_secret_key # Optional: For signature validation
WEBHOOK_1_EVENTS=payment.*,subscription.* # Required: Event patterns
WEBHOOK_1_HEADERS={"Authorization":"Bearer token"} # Optional: Custom headers (JSON)
WEBHOOK_1_TIMEOUT=30 # Optional: Timeout in seconds
WEBHOOK_1_RETRIES=3 # Optional: Retry attemptsMultiple Webhooks ​
Add as many webhooks as you need by incrementing the number:
# Discord for alerts
WEBHOOK_1_NAME=discord
WEBHOOK_1_URL=https://discord.com/api/webhooks/123/abc
WEBHOOK_1_EVENTS=payment.failed
# Slack for notifications
WEBHOOK_2_NAME=slack
WEBHOOK_2_URL=https://hooks.slack.com/services/T00/B00/XXX
WEBHOOK_2_EVENTS=payment.success,subscription.created
# Your internal API
WEBHOOK_3_NAME=internal_api
WEBHOOK_3_URL=https://api.yourcompany.com/webhooks/payments
WEBHOOK_3_SECRET=your_api_secret
WEBHOOK_3_EVENTS=*
WEBHOOK_3_HEADERS={"Authorization":"Bearer your-token"}
# Analytics platform
WEBHOOK_4_NAME=analytics
WEBHOOK_4_URL=https://analytics.platform.com/events
WEBHOOK_4_EVENTS=payment.*,customer.*Event Types ​
Payment Events ​
payment_intent.succeeded- Successful paymentpayment_intent.payment_failed- Failed paymentpayment_intent.canceled- Canceled paymentcharge.refund.created- Refund processed
Subscription Events ​
customer.subscription.created- New subscriptioncustomer.subscription.updated- Subscription modifiedcustomer.subscription.deleted- Subscription canceledinvoice.payment_failed- Subscription payment failed
Customer Events ​
customer.created- New customercustomer.updated- Customer information changedcustomer.deleted- Customer removed
Event Patterns ​
Use patterns to match multiple events:
# All payment events
WEBHOOK_1_EVENTS=payment.*
# All subscription events
WEBHOOK_2_EVENTS=subscription.*
# All customer events
WEBHOOK_3_EVENTS=customer.*
# Everything
WEBHOOK_4_EVENTS=*
# Specific events only
WEBHOOK_5_EVENTS=payment_intent.succeeded,customer.subscription.createdWebhook Payload ​
Payload Structure ​
{
"event": "payment_intent.succeeded",
"data": {
"webhook_id": "wh_stripe_123456",
"event_data": {
"id": "evt_stripe_789",
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_payment_123",
"amount": 5000,
"currency": "usd",
"status": "succeeded"
}
}
},
"payment_id": "pi_payment_123",
"customer_id": "cus_customer_456",
"timestamp": "2024-01-15T10:30:00Z",
"payment": {
"id": "pay_bridge_123",
"amount_cents": 5000,
"currency": "USD",
"status": "succeeded",
"concept": "Subscription Payment"
},
"customer": {
"id": "cus_bridge_456",
"email": "[email protected]",
"name": "John Doe"
}
}
}Integration Examples ​
Discord Notifications ​
WEBHOOK_1_NAME=discord_payments
WEBHOOK_1_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN
WEBHOOK_1_EVENTS=payment.failed,subscription.cancelledDiscord Webhook Handler:
// Your Discord webhook receives:
{
"content": "💳 Payment Event: payment_intent.succeeded",
"embeds": [{
"title": "Payment Successful",
"description": "Payment of $50.00 completed",
"color": 3066993,
"fields": [
{ "name": "Customer", "value": "[email protected]" },
{ "name": "Amount", "value": "$50.00" },
{ "name": "Status", "value": "succeeded" }
]
}]
}Slack Notifications ​
WEBHOOK_2_NAME=slack_payments
WEBHOOK_2_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
WEBHOOK_2_EVENTS=payment.success,subscription.createdCustom API Endpoint ​
WEBHOOK_3_NAME=internal_api
WEBHOOK_3_URL=https://api.yourcompany.com/webhooks/payments
WEBHOOK_3_SECRET=your_webhook_secret
WEBHOOK_3_EVENTS=*
WEBHOOK_3_HEADERS={"Authorization":"Bearer your-api-token","X-Custom-Header":"value"}API Handler Example (Node.js/Express):
import express from 'express';
import crypto from 'crypto';
const app = express();
app.post('/webhooks/payments', express.json(), (req, res) => {
// 1. Verify signature (if secret is configured)
const signature = req.headers['x-webhook-signature'];
const secret = process.env.WEBHOOK_SECRET;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(req.body))
.digest('hex');
if (signature !== expectedSignature) {
return res.status(401).send('Invalid signature');
}
// 2. Process event
const { event, data } = req.body;
switch (event) {
case 'payment_intent.succeeded':
console.log('Payment succeeded:', data.payment);
// Update your database, send email, etc.
break;
case 'payment_intent.payment_failed':
console.log('Payment failed:', data.payment);
// Send notification, retry logic, etc.
break;
case 'customer.subscription.created':
console.log('New subscription:', data.subscription);
// Grant access, send welcome email, etc.
break;
}
// 3. Respond with 200 OK
res.status(200).send('OK');
});Security ​
Signature Validation ​
When you configure a WEBHOOK_X_SECRET, Bridge Payments signs each webhook request:
WEBHOOK_1_SECRET=your_secret_keyVerify signature in your endpoint:
import crypto from 'crypto';
function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return signature === expectedSignature;
}
// In your handler
const signature = req.headers['x-webhook-signature'];
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}Best Practices ​
- Always verify signatures when using secrets
- Use HTTPS for webhook URLs
- Respond quickly (within 5 seconds) to avoid timeouts
- Process asynchronously for long-running tasks
- Handle retries gracefully (check for duplicate events)
- Log all webhook events for debugging
Retry Logic ​
Bridge Payments automatically retries failed webhook deliveries:
- Default retries: 3 attempts
- Retry delays: Exponential backoff (1s, 2s, 4s)
- Timeout: 30 seconds per attempt (configurable)
Configure retry behavior:
WEBHOOK_1_RETRIES=5 # Number of retry attempts
WEBHOOK_1_TIMEOUT=60 # Timeout in secondsDebugging ​
Enable debug mode to see webhook delivery logs:
WEBHOOK_DEBUG_MODE=trueLogs will show:
- Webhook configuration loaded
- Event matching
- HTTP requests sent
- Response status codes
- Retry attempts
Next Steps ​
- Testing - Test webhook deliveries
- API Reference - Webhook API documentation
- Configuration - Environment configuration