Gareth Wilson Gareth Wilson

Guide to Paystack Webhooks: Features and Best Practices

Published


Paystack webhooks notify your backend when payment activity happens: a charge succeeds, a transfer completes, a subscription is created. Because a webhook is often the authoritative signal that money moved, verifying and handling them correctly is core to any Paystack integration.

This guide covers how Paystack webhooks work, the events you'll handle, how to verify the x-paystack-signature (which is HMAC-SHA512, not SHA256), the source IPs and retry behavior, and the best practices for production.

What are Paystack webhooks?

Paystack webhooks are HTTP POSTs sent to a URL you configure, each carrying a JSON body with a top-level event (a dot-separated name) and a data object. You configure the webhook URL per mode (test and live are separate), and Paystack signs every delivery so you can confirm it came from Paystack.

The signature uses SHA-512, which is the detail most integrations get wrong on the first try.

Paystack webhook features

FeatureDetails
ConfigurationDashboard > Settings > API Keys & Webhooks (test and live separate)
Signature headerx-paystack-signature
Signature schemeHMAC-SHA512 of the raw body, keyed with your secret key, hex-encoded
PayloadTop-level event (dot-separated) + data
Source IPs52.31.139.75, 52.49.173.169, 52.214.14.220 (test and live)
AcknowledgementRespond 200 immediately; request timeout 30 seconds
Retries (live)Every 3 min for the first 4 tries, then hourly for up to 72 hours
Retries (test)Hourly for 10 hours
SDKOfficial clients are general API clients; no verification helpers

Common events

Paystack events are dot-separated names. The core ones:

EventFires when
charge.successA charge is completed successfully
transfer.successA transfer completes
transfer.failedA transfer fails
transfer.reversedA transfer is reversed
subscription.createA subscription is created
subscription.disableA subscription is disabled
invoice.updateAn invoice is updated
refund.processedA refund is processed

Subscribe your handler to the events you process, and consult Paystack's supported-events reference for the full list.

Setting up Paystack webhooks

Set the webhook URL in Dashboard > Settings > API Keys & Webhooks. Test and live modes have separate webhook URLs, so configure both. Optionally allowlist Paystack's source IPs (52.31.139.75, 52.49.173.169, 52.214.14.220, the same for test and live) at your firewall. Respond 200 immediately and defer work; the request times out after 30 seconds.

Securing Paystack webhooks

Every delivery carries an x-paystack-signature header: a hex-encoded HMAC-SHA512 of the request body, keyed with your Paystack secret key. Note the algorithm: it's SHA-512, not the SHA-256 most providers use. Verify against the raw request body.

Paystack's own documentation example hashes JSON.stringify(req.body), which works only if your framework reproduces the exact bytes Paystack sent. To avoid re-serialization mismatches, hash the raw body directly.

const crypto = require("crypto");

const SECRET_KEY = process.env.PAYSTACK_SECRET_KEY;

function verify(rawBody, signature) {
  const expected = crypto
    .createHmac("sha512", SECRET_KEY)
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers["x-paystack-signature"])) {
    return res.sendStatus(401);
  }

  res.sendStatus(200); // acknowledge immediately
  processQueue.add(JSON.parse(req.body)); // process asynchronously
});

The same check in Python:

import hashlib
import hmac
import os

SECRET_KEY = os.environ["PAYSTACK_SECRET_KEY"].encode()


def verify(raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(SECRET_KEY, raw_body, hashlib.sha512).hexdigest()
    return hmac.compare_digest(expected, signature or "")

Paystack webhook limitations and pain points

It's SHA-512, over the raw body

The Problem: x-paystack-signature is HMAC-SHA512. A SHA-256 verifier (the common default) fails every delivery. And hashing a re-serialized body instead of the raw bytes produces a mismatch even with the right algorithm.

Why It Happens: Paystack chose SHA-512, and its example hashes a stringified body, which hides the raw-body requirement.

Workarounds:

  • Use sha512 and verify against the exact raw body.
  • Compare in constant time.

How Hookdeck Can Help: Hookdeck verifies the SHA-512 signature at the edge against the raw bytes, so your application receives pre-verified events without the algorithm or serialization gotchas.

No timestamp, so no replay window

The Problem: The signature covers the body only, with no timestamp, so a captured, validly signed payload can be replayed and still verify.

Why It Happens: Paystack's scheme doesn't include a signed timestamp.

Workarounds:

  • Make processing idempotent and dedupe on event plus data.id/reference.
  • Optionally restrict to Paystack's source IPs to reduce exposure.

How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge, filtering replays and retries before they reach your app. See our guide to webhook idempotency.

Retries bring duplicates

The Problem: Live retries run every 3 minutes for the first 4 attempts, then hourly for up to 72 hours (test retries hourly for 10 hours). The same event can arrive many times, and there's no documented idempotency header.

Why It Happens: At-least-once delivery favors eventual delivery of payment events.

Workarounds:

  • Dedupe on event + data.id/reference.
  • Make downstream effects idempotent (fulfillment, ledger writes).

How Hookdeck Can Help: Hookdeck deduplicates and durably queues deliveries, so the 72-hour retry window doesn't cause double-fulfillment. See our guide to webhook idempotency.

Verify the webhook before trusting the amount

The Problem: Treating the webhook payload as the source of truth for how much was paid, without verifying, risks acting on a forged or replayed event.

Why It Happens: Webhooks are a notification, and a payment amount is exactly the kind of value an attacker would forge.

Workarounds:

  • Always verify the signature first, then confirm the transaction by calling Paystack's verify endpoint before fulfilling.
  • Reconcile against the API for high-value flows.

How Hookdeck Can Help: Hookdeck ensures only verified events reach your app, and its observability lets you trace exactly what was delivered when reconciling payments.

Best practices

Verify with HMAC-SHA512 over the raw body

Use sha512, key it with your secret key, verify against the exact raw body, and compare in constant time.

Acknowledge immediately, process asynchronously

Return 200 right away and defer work to a queue, well inside the 30-second timeout. See why to process webhooks asynchronously.

Confirm the transaction before fulfilling

After verifying the signature, call Paystack's verify endpoint to confirm the amount and status before granting value.

Dedupe on event + reference

Use event plus data.id/reference as an idempotency key so the retry window doesn't double-process.

Configure test and live separately

Set both webhook URLs, and optionally allowlist Paystack's source IPs.

Make Paystack webhooks production-ready

Hookdeck verifies the SHA-512 signature, deduplicates, and durably queues every payment event

Conclusion

Paystack webhooks are often the authoritative signal that a payment happened, so verifying them correctly matters, and the detail that catches people is the algorithm: HMAC-SHA512 over the raw body, keyed with your secret key. Add the lack of a replay window, a 72-hour retry schedule that brings duplicates, and separate test/live configuration, and there's real care required before you fulfill on an event.

That leaves correct verification, transaction confirmation, deduplication, and fast acknowledgement on you. Hookdeck verifies the SHA-512 signature, deduplicates, and durably queues every event at the edge, so your backend only ever acts on verified, unique payment events.

Get started with Hookdeck for free and handle Paystack webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.