Skip to content

Verify PhonePe webhooks

Verify that a webhook really came from PhonePe (India) before acting on it.

Identifier 'phonepe'
Import import { verifyPhonePe } from 'verihook/phonepe'
Headers authorization
Signature The header is the hex SHA-256 of <username>:<password>.
Replay window None (the provider sends no timestamp)
result.eventType the body’s event, e.g. checkout.order.completed

The username and password you set for the webhook in the PhonePe Business dashboard, passed as "username:password".

Store it in an environment variable (PHONEPE_WEBHOOK_CREDENTIALS below) and never commit it.

import { verifyPhonePe } from 'verihook/phonepe';
export async function POST(request: Request) {
const result = await verifyPhonePe(request, process.env.PHONEPE_WEBHOOK_CREDENTIALS!);
if (!result.valid) {
console.warn(result.code, result.reason, result.hint);
return new Response('Invalid signature', { status: 401 });
}
console.log(result.eventType, result.event);
return Response.json({ received: true });
}

request can be a Fetch Request or { headers, body, url? } with the raw body. verifyWebhook('phonepe', request, secret) from verihook does the same. Read why the raw body matters if verification fails behind a body parser.

app/api/webhooks/phonepe/route.ts
import { createWebhookHandler } from 'verihook/next';
export const POST = createWebhookHandler('phonepe', process.env.PHONEPE_WEBHOOK_CREDENTIALS!, async (payload, result) => {
// Runs only for a valid signature
console.log(result.eventType, result.event);
});

Each adapter responds 401 to an invalid signature, 413 to a body over maxBodySize (2 MB by default) and 200 to a duplicate when you pass a dedupeStore.

  • PhonePe doesn’t sign the body. The header proves the sender knows your credentials, but confirm the order status with PhonePe’s API before fulfilling it.

signWebhook from verihook/testing builds a correctly signed PhonePe request for your tests:

import { signWebhook } from 'verihook/testing';
import { verifyPhonePe } from 'verihook/phonepe';
const hook = await signWebhook('phonepe', {
secret: 'phonepe_user:phonepe_pass',
payload: { id: 'evt_test' },
});
const result = await verifyPhonePe(
{ headers: hook.headers, body: hook.body, url: hook.url },
hook.secret,
);
// result.valid === true

From the command line, npx verihook simulate phonepe --url http://localhost:3000/webhooks/phonepe sends one to your local server.

See Testing your handlers for supertest and Fetch Request examples.

Failed results carry a code and often a hint with the likely cause. See Troubleshooting for each error code.

PhonePe’s webhook documentation