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-signature header?
  • Why is my Paystack webhook signature verification failing?
  • How do I handle charge.success, transfer.success, or subscription.create events?
  • 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.

EventTriggered When
charge.successA payment (charge) is successful
transfer.successA transfer to a recipient succeeds
transfer.failedA transfer fails
transfer.reversedA transfer is reversed
refund.processedA refund has been completed
subscription.createA subscription is created
subscription.disableA subscription is disabled/cancelled
invoice.createAn invoice is created for a subscription charge
invoice.updateAn invoice is updated after a charge attempt
invoice.payment_failedA subscription invoice payment fails
charge.dispute.createA 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Aug 2, 2026

View on GitHub →