Skip to content

Paystack Webhook Checker

Paste the body Paystack sent to your webhook URL. You'll see what the event means, whether x-paystack-signature matches, the exact request that confirms it, and what your code should do next. Nothing leaves your browser.

Webhook body

Try a sample:
Check the signature (optional, test key only)

The HMAC is computed in your browser. Nothing is sent or saved, and live keys are refused. Paste the body exactly as your server logged it: one changed space or line break gives a different signature.

Compare with your order (optional, recommended)

Enter the amount as on your order (25.50). The tool compares it in subunits (2550), as Paystack sends it.

Result

What your handler should do

    Confirm it with Paystack

      Fields in this webhook
      FieldValueMeaning

      Safe webhook handler

      Checks the signature over the raw body in constant time, reads only data.reference, verifies with Paystack, and never fulfils an order twice.

      How Paystack webhooks work

      When something happens on your account, Paystack sends a POST to the webhook URL in Settings → API Keys & Webhooks. The body is {"event": "...", "data": {...}}. For a payment the event is charge.success, anddata.reference is the reference you chose when you created the charge. For a payout it is transfer.success,transfer.failed or transfer.reversed.

      Every request carries x-paystack-signature: an HMAC SHA512 of the body, signed with your secret key. There is no separate webhook secret. Test-mode webhooks are signed with your test key, live ones with your live key. Amounts are in the currency's subunit: 2550 is 25.50 GHS.

      Five rules for a safe handler

      1. Check the signature over the raw body. Parse the JSON only after the check; refuse (401) if it fails.
      2. Don't trust the body. Read only data.reference, then call GET /transaction/verify/:reference (or /transfer/verify/:reference).
      3. Check the verified transaction. The reference must be yours, status must be success, the currency must match, and the amount must equal your order in subunits.
      4. Answer 200 quickly. Paystack retries until it gets one: every 3 minutes for the first 4 tries in live mode, then hourly for 72 hours.
      5. Be idempotent. Retries repeat events. Update an order only while it is still pending.

      Paystack sends charge webhooks only for successful payments, so don't wait for a failure webhook. Check pending orders from a scheduled job with the verify endpoint. For payouts, remember that a successful transfer can still be reversed later.

      Common problems

      • The signature never matches. A JSON middleware parsed the body first. In Express use express.raw() on the webhook route; in Laravel use $request->getContent(); in Django use request.body.
      • It matched in test mode but not live. Your server still uses the test key. Live webhooks are signed with the live key.
      • The amount is 100 times too small. The charge was created in major units. Send amount in subunits.
      • Webhooks stop arriving. Your endpoint returns an error or redirects. Check that it answers 200 over HTTPS.

      Facts from Paystack's documentation (Webhooks, Transfers and Verify transaction), checked November 2026. The samples are illustrative: their shapes follow Paystack's verify responses.

      Frequently asked questions

      How do I verify a Paystack webhook signature?

      Compute an HMAC SHA512 of the raw request body, keyed with your secret key, as hex. Compare it with the x-paystack-signature header using a constant-time comparison, and refuse the request (401) if they differ. Hash the exact bytes you received, not a parsed and re-serialised copy.

      Why does my x-paystack-signature never match?

      Usually because the body was parsed before the check: express.json(), a framework middleware or a JSON re-encode changes the bytes. Read the raw body on the webhook route. The other common cause is using the secret key of the other mode: test webhooks are signed with your test key, live ones with your live key.

      Does Paystack send a webhook when a payment fails?

      Not for charges: Paystack sends charge webhooks only for successful payments (charge.success). Check pending orders from a scheduled job with GET /transaction/verify/:reference so failed and abandoned ones get settled. Transfers are different: transfer.failed and transfer.reversed are sent.

      How often does Paystack retry a webhook?

      Until your server answers 200. In live mode, every 3 minutes for the first 4 tries, then hourly for the next 72 hours. In test mode, hourly for 72 hours. Answer 200 first and do slow work after, and make your handler idempotent.

      Is it safe to paste my secret key here?

      Only a test key, and only if you want the signature checked. The HMAC is computed in your browser with Web Crypto; nothing is sent or saved. The page refuses live keys (sk_live_...). Leave the key empty and every other check still works.

      Related

      Need this built for you?

      I integrate MTN MoMo, M-Pesa and Airtel Money into Laravel / PHP, Python / Django, Node.js / Next.js apps: payments, callbacks, payouts and go-live, with tests.