Agent skill
Paystack Webhooks Skill
Receive and verify Paystack webhooks. Use when setting up Paystack webhook handlers, debugging x-paystack-signature verification, or handling payment events like charge.success, transfer.success, transfer.failed, refund.processed, subscription.create, or invoice.payment_failed.
Install this skill
npx skills add hookdeck/webhook-skills --skill paystack-webhooks
Paystack is an African payments platform. It notifies your application of payment lifecycle events (charges, transfers, refunds, subscriptions, invoices, disputes) by sending an HTTP POST webhook with a JSON payload to your endpoint.
When to Use This Skill
- How do I receive Paystack webhooks?
- How do I verify the
x-paystack-signatureheader? - Why is my Paystack webhook signature verification failing?
- How do I handle
charge.success,transfer.success, orsubscription.createevents? - Understanding Paystack event types and payload structure
Verification (core)
Paystack signs each webhook with HMAC-SHA512 over the raw request body, hex-encoded, in the x-paystack-signature header. The key is your Paystack secret key (sk_test_… / sk_live_…) — the same key you use for API calls. Verify the raw body — do not JSON.parse before verifying.
The official Paystack SDKs are general API clients with no webhook verification helper, so verify manually. In Node.js (Express, Next.js):
const crypto = require('crypto');
// rawBody: the raw HTTP body as a string/Buffer (NOT parsed JSON)
// signature: value of the x-paystack-signature header
// secret: PAYSTACK_SECRET_KEY (sk_test_… / sk_live_…)
function verifyPaystackWebhook(rawBody, signature, secret) {
if (!signature) return false;
const expected = crypto.createHmac('sha512', secret).update(rawBody).digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
} catch {
return false; // length mismatch → invalid
}
}
In Python (FastAPI):
import hmac, hashlib
expected = hmac.new(secret.encode(), raw_body, hashlib.sha512).hexdigest()
is_valid = hmac.compare_digest(expected, signature_header)
For complete handlers with route wiring, event dispatch, and tests, see:
Common Event Types
The event type is in the JSON body's event field (dot-separated), not a header.
| Event | Triggered When |
|---|---|
charge.success | A payment (charge) is successful |
transfer.success | A transfer to a recipient succeeds |
transfer.failed | A transfer fails |
transfer.reversed | A transfer is reversed |
refund.processed | A refund has been completed |
subscription.create | A subscription is created |
subscription.disable | A subscription is disabled/cancelled |
invoice.create | An invoice is created for a subscription charge |
invoice.update | An invoice is updated after a charge attempt |
invoice.payment_failed | A subscription invoice payment fails |
charge.dispute.create | A dispute (chargeback) is opened |
For the full event reference, see references/overview.md and Paystack's webhook docs.
Environment Variables
PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Dashboard → Settings → API Keys & Webhooks
The signing key is your secret key — the same sk_test_… / sk_live_… key used for API requests. Test mode and live mode have separate keys; a signature is valid only against the key for the mode that sent it.
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 paystack --path /webhooks/paystack
Reference Materials
- references/overview.md - Paystack webhook concepts, events, payload structure
- references/setup.md - Dashboard configuration, secret key, IP allowlist, retries
- references/verification.md - Signature verification details and gotchas