agentsclimarketplace

Paystack webhooks

Skill rexedge/paystack/skills/paystack-webhooks

24 AI agent skills for Paystack API integration — automate payment workflows with TypeScript, Node.js, and Next.js

Install
npx -y skills add rexedge/paystack --skill paystack-webhooks

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Paystack webhook integration — signature validation with HMAC SHA512, event parsing, IP whitelisting, retry policy, and all supported event types. Use this skill whenever setting up a webhook endpoint for Paystack, validating x-paystack-signature headers, handling charge.success or transfer.success events, debugging webhook delivery failures, implementing idempotent event processing, or building any server-side Paystack event listener. Also use when encountering webhook timeout issues or needing the list of Paystack webhook IP addresses.

SKILL.md

7.8 KB, as published. Nobody here has run it

Paystack Webhooks

Webhooks let Paystack push real-time event notifications to your server. They are the recommended way to confirm payment status — more reliable than client-side callbacks or polling.

Depends on: paystack-setup for environment configuration.

How Webhooks Work

Customer pays → Paystack processes → Paystack POSTs event JSON to your webhook URL
                                   → Your server validates signature
                                   → Returns 200 OK immediately
                                   → Then processes the event asynchronously

Endpoints

Your webhook URL is a POST endpoint you create on your server. Register it on the Paystack Dashboard under Settings → API Keys & Webhooks.

Signature Validation

Every webhook request includes an x-paystack-signature header containing an HMAC SHA512 hash of the request body, signed with your secret key. Always validate this before processing.

Next.js App Router (Route Handler)

// app/api/webhooks/paystack/route.ts
import crypto from "crypto";
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get("x-paystack-signature");
  
  const hash = crypto
    .createHmac("sha512", process.env.PAYSTACK_SECRET_KEY!)
    .update(body)
    .digest("hex");

  if (hash !== signature) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
  }

  // Return 200 immediately — process event asynchronously
  const event = JSON.parse(body);

  // Handle event based on type
  switch (event.event) {
    case "charge.success":
      await handleChargeSuccess(event.data);
      break;
    case "transfer.success":
      await handleTransferSuccess(event.data);
      break;
    case "transfer.failed":
      await handleTransferFailed(event.data);
      break;
    // ... handle other events
  }

  return NextResponse.json({ received: true }, { status: 200 });
}

async function handleChargeSuccess(data: any) {
  const { reference, amount, customer, metadata } = data;
  // Verify the transaction server-side as an extra check
  // Update your database, fulfill the order, etc.
}

async function handleTransferSuccess(data: any) {
  const { reference, amount, recipient } = data;
  // Mark transfer as completed in your database
}

async function handleTransferFailed(data: any) {
  const { reference, amount } = data;
  // Mark transfer as failed, notify admin, retry if needed
}

Express.js

import crypto from "crypto";
import express from "express";

const app = express();
app.use(express.json());

app.post("/webhooks/paystack", (req, res) => {
  const hash = crypto
    .createHmac("sha512", process.env.PAYSTACK_SECRET_KEY!)
    .update(JSON.stringify(req.body))
    .digest("hex");

  if (hash !== req.headers["x-paystack-signature"]) {
    return res.status(401).send("Invalid signature");
  }

  // Return 200 immediately
  res.sendStatus(200);

  // Process event asynchronously
  const event = req.body;
  processEvent(event).catch(console.error);
});

IP Whitelisting

As an additional security layer, only allow requests from Paystack's IP addresses:

52.31.139.75
52.49.173.169
52.214.14.220

These IPs apply to both test and live environments.

const PAYSTACK_IPS = ["52.31.139.75", "52.49.173.169", "52.214.14.220"];

function isPaystackIP(ip: string): boolean {
  // Handle x-forwarded-for if behind a proxy/load balancer
  const clientIP = ip.split(",")[0].trim();
  return PAYSTACK_IPS.includes(clientIP);
}

Retry Policy

If your webhook endpoint doesn't return a 200 OK status, Paystack retries:

ModeRetry ScheduleDuration
LiveEvery 3 minutes for first 4 tries, then hourlyUp to 72 hours
TestHourlyUp to 10 hours

Request timeout is 30 seconds in test mode. Return 200 OK immediately and process events asynchronously to avoid timeouts.

Idempotency

Webhook events may be sent more than once. Make your handler idempotent:

async function handleChargeSuccess(data: any) {
  const { reference } = data;

  // Check if already processed
  const existing = await db.transaction.findUnique({ where: { reference } });
  if (existing?.status === "completed") {
    return; // Already processed, skip
  }

  // Process and mark as completed atomically
  await db.transaction.upsert({
    where: { reference },
    update: { status: "completed", paidAt: new Date() },
    create: { reference, status: "completed", amount: data.amount, paidAt: new Date() },
  });
}

Supported Event Types

EventDescription
charge.successA successful charge/payment was made
charge.dispute.createA dispute was logged against your business
charge.dispute.remindA logged dispute hasn't been resolved
charge.dispute.resolveA dispute has been resolved
customeridentification.failedCustomer ID validation failed
customeridentification.successCustomer ID validation succeeded
dedicatedaccount.assign.failedDVA couldn't be created/assigned
dedicatedaccount.assign.successDVA successfully created/assigned
invoice.createInvoice created for a subscription (3 days before due)
invoice.payment_failedInvoice payment failed
invoice.updateInvoice updated (usually after successful charge)
paymentrequest.pendingPayment request sent to customer
paymentrequest.successPayment request paid
refund.failedRefund failed — account credited with refund amount
refund.pendingRefund initiated, awaiting processor
refund.processedRefund successfully processed
refund.processingRefund received by processor
subscription.createSubscription created
subscription.disableSubscription disabled
subscription.expiring_cardsMonthly notice of subscriptions with expiring cards
subscription.not_renewSubscription set to non-renewing
transfer.successTransfer completed successfully
transfer.failedTransfer failed
transfer.reversedTransfer reversed

Event Payload Structure

Every webhook event follows this structure:

{
  "event": "charge.success",
  "data": {
    "id": 4099260516,
    "domain": "live",
    "status": "success",
    "reference": "re4lyvq3s3",
    "amount": 50000,
    "currency": "NGN",
    "channel": "card",
    "customer": {
      "id": 82796315,
      "email": "[email protected]",
      "customer_code": "CUS_xxxxx"
    },
    "authorization": {
      "authorization_code": "AUTH_xxxxx",
      "card_type": "visa",
      "last4": "4081",
      "reusable": true
    },
    "metadata": {}
  }
}

Go-Live Checklist

  1. Add the webhook URL on your Paystack dashboard (Settings → API Keys & Webhooks)
  2. Ensure the URL is publicly accessible (localhost won't receive events)
  3. If using .htaccess, add a trailing / to the URL
  4. Validate signature on every request using x-paystack-signature
  5. Return 200 OK immediately before processing long-running tasks
  6. Make handlers idempotent — events can be sent more than once
  7. Test with Paystack's test mode before going live

Gives 0 of the 12 instructions most apis services skills give

Counted across 424 of the 426 authors here whose files we hold, read 2026-08-06

  • use plural nouns for resource namesin 41 of 424, across 32 files
  • use cursor-based pagination for large datasetsin 35 of 424, across 20 files
  • include rate limit headers in responsesin 25 of 424, across 13 files
  • Use kebab-case for multi-word resourcesin 23 of 424, across 13 files
  • version APIs in the URL pathin 19 of 424, across 9 files
  • use semantic HTTP status codesin 18 of 424, across 8 files
  • verify webhook signaturesin 18 of 424, across 11 files
  • use query parameters for filteringin 17 of 424, across 6 files
  • use async database operationsin 14 of 424, across 7 files
  • wrap successful responses in a data fieldin 13 of 424, across 3 files
  • prefix sorting parameters with a hyphen for descending orderin 13 of 424, across 3 files
  • set appropriate HTTP status codesin 13 of 424, across 6 files

Said here and by no other author read

  • validate signature before processing
  • whitelist paystack ip addresses
  • check for existing processed transactions
  • add trailing slash if using htaccess
  • test in test mode before going live

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.