Webhooks — integration guide

This guide covers production-grade webhook handling. For the API reference (event types, payload format, signature verification), see API → Webhooks.

Local development

Webhook endpoints need to be publicly reachable. During development, use the NahuPay CLI to forward events to your local server:

# Install the CLI
npm install -g nahupay-cli

# Log in
nahupay login

# Forward to your local handler
nahupay listen --forward-to localhost:3000/api/webhooks/nahupay

The CLI prints a webhook secret specific to your forwarding session. Use this as NAHUPAY_WEBHOOK_SECRET in your .env.local.

The forwarding secret differs from your production webhook secret — use a separate environment variable for each.

Register an endpoint

  1. Go to Dashboard → Settings → Webhooks → Add endpoint
  2. Enter your HTTPS URL (e.g. https://api.myshop.com/webhooks/nahupay)
  3. Select which events to receive (or choose "All events")
  4. Copy the generated webhook secret — you won't see it again

You can also register endpoints programmatically:

const endpoint = await client.webhookEndpoints.create({url:    'https://api.myshop.com/webhooks/nahupay',events: ['payment.completed', 'payment.refunded'],});console.log(endpoint.secret);  // store this immediately

Idempotency

NahuPay will retry events if your endpoint is temporarily unreachable. Your handler must be idempotent — safe to call multiple times with the same event. The simplest approach is to store processed event IDs in a database:

export async function POST(req: Request) {const event = verifyAndParse(req);// Deduplicate using the event IDconst seen = await db.webhookEvents.findUnique({where: { id: event.id },});if (seen) return Response.json({ received: true });await db.webhookEvents.create({data: { id: event.id, type: event.type, processed_at: new Date() }});await processEvent(event);return Response.json({ received: true });}

Async processing

Your endpoint has a 5-second window to return 200. For operations that take longer (database writes, sending emails, calling third-party APIs), return 200 immediately and process in the background:

export async function POST(req: Request) {const event = verifyAndParse(req);// Enqueue for background processing (e.g. BullMQ, Inngest, Trigger.dev)await queue.add('process-payment-event', event);return Response.json({ received: true });  // instant reply}

Replaying events

If an event fails all retry attempts, you can manually replay it from Dashboard → Webhooks → [your endpoint] → Failed events → Replay.

You can also replay any event by ID via the API, regardless of its delivery status — useful for backfilling after a deployment:

await client.webhookEndpoints.deliverEvent('we_endpoint_abc',{ event_id: 'evt_01hxbcdef' });