Documentation
Paystack Webhook Dispatcher Guide
This service acts as a "Traffic Controller" for Paystack webhooks. Since Paystack only allows one global Webhook URL, this dispatcher receives all events and routes them to the correct application based on the metadata.app_name field.
Setup
1. Environment Variables
Add the following to your environment variables (Vercel or .env.local):
# The secret key from your Paystack Dashboard (Settings > API Keys & Webhooks)PAYSTACK_SECRET=sk_live_xxxxxxx# Routing Table: Map metadata.app_name to destination URLs # Pattern: PAYSTACK_FORWARD_URL_[APP_NAME]=[URL] # The app name in the environment variable should be uppercase and non-alphanumeric characters replaced with underscores. # Example mappings:PAYSTACK_FORWARD_URL_SAAS_APP=https://saas.f8ware.com/api/webhooksPAYSTACK_FORWARD_URL_ECOMMERCE=https://shop.f8ware.com/api/webhooks2. Paystack Configuration
- Log in to your Paystack Dashboard.
- Go to Settings > API Keys & Webhooks.
- Set the Webhook URL to
https://api.f8ware.com/api/webhooks/paystack.
3. Usage in Applications
When initiating a transaction (e.g., via Paystack Inline or API), ensure you include the app_name in the metadata:
const response = await paystack.transaction.initialize({
email: "customer@example.com",
amount: 10000,
metadata: {
app_name: "saas_app" // This must match the ENV variable suffix
}
});Adding a New Application
When you launch a new application (e.g., store.f8ware.com) and want it to receive Paystack webhooks through the central dispatcher, follow this two-step process:
Step 1: Update Dispatcher Environment Variables
On the central dispatcher server (api.f8ware.com), add a new environment variable pointing to your new application. If we decide to use "store_app" as the identifier:
PAYSTACK_FORWARD_URL_STORE_APP=https://store.f8ware.com/api/webhooksThe dispatcher automatically converts your identifier to uppercase and replaces non-alphanumeric characters with underscores to look up this key.
Step 2: Update Client Application Metadata
On your new store application (store.f8ware.com), include the matching app_name inside the transaction metadata when initializing payments:
const response = await paystack.transaction.initialize({
email: "customer@store.com",
amount: 5000,
metadata: {
app_name: "store_app", // <--- The crucial part
cart_id: "12345" // Your standard metadata
}
});Security
- Signature Verification: The dispatcher verifies every request using HMAC SHA512 and your
PAYSTACK_SECRET. - Payload Integrity: The raw request body is used for verification to ensure no tampering.
- Forwarding: The dispatcher forwards the original
x-paystack-signatureheader so downstream applications can also verify the request if they choose.
Error Handling
Invalid or missing signature.
Missing metadata.app_name or malformed JSON.
No destination URL configured for the provided app_name.
Forwarding to the destination URL failed.
Testing
You can run the internal tests to verify the logic:
npx tsx tests/webhook.test.ts