I have tried to set up verification as shown here and I keep getting the error: { error: 'invalid_token', error_description: 'Token signature verification failed' }
.
Here is what my verification function looks like:
export const verifyPayPalWebhook = async (req: NextApiRequest) => {
const url =
"https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature";
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.PAYPAL_CLIENT_SECRET}`,
},
body: JSON.stringify({
transmission_id: req.headers["paypal-transmission-id"],
transmission_time: req.headers["paypal-transmission-time"],
cert_url: req.headers["paypal-cert-url"],
auth_algo: req.headers["paypal-auth-algo"],
transmission_sig: req.headers["paypal-transmission-sig"],
webhook_id: process.env.PAYPAL_WEBHOOK_ID,
webhook_event: req.body,
}),
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error_description);
}
return data.verification_status === "SUCCESS";
};
I have confirmed that both PAYPAL_CLIENT_SECRET
and PAYPAL_WEBHOOK_ID
environment variables are defined. The PAYPAL_CLIENT_SECRET
is the 'secret' for my application and PAYPAL_WEBHOOK_ID
is the webhook ID for the specific webhook.
I am testing my webhook endpoint by creating and capturing an order on my development server that will eventually notify the webhook endpoint.
To make REST API calls, you first need to use the secret and corresponding client_id to obtain a valid oauth2 token (which can be cached for 9 hours if desired)
The easiest way to do it in node is to reuse the sample function from https://developer.paypal.com/docs/checkout/standard/integrate/
// For a fully working example, please see:
// https://github.com/paypal-examples/docs-examples/tree/main/standard-integration
const { CLIENT_ID, APP_SECRET } = process.env;
const baseURL = {
sandbox: "https://api-m.sandbox.paypal.com",
production: "https://api-m.paypal.com"
};
// generate an access token using client id and app secret
async function generateAccessToken() {
const auth = Buffer.from(CLIENT_ID + ":" + APP_SECRET).toString("base64")
const response = await fetch(`${baseURL.sandbox}/v1/oauth2/token`, {
method: "POST",
body: "grant_type=client_credentials",
headers: {
Authorization: `Basic ${auth}`,
},
});
const data = await response.json();
return data.access_token;
}