Back to blog

Validate Stripe Webhook Signatures using request raw body

Date review:

When handling Stripe webhooks, it’s crucial to verify that the requests actually come from Stripe. This prevents malicious actors from sending fake payment notifications to your system.

Stripe uses HMAC-SHA256 signatures to ensure webhook authenticity, and YepCode’s raw body access makes verification straightforward and secure.

Setup

  1. Create a webhook endpoint in your Stripe Dashboard pointing to your YepCode webhook:
https://cloud.yepcode.io/api/<your-team>/webhooks/<your-process-slug>
  1. Copy the webhook signing secret from Stripe (starts with whsec_)
  2. Store the secret securely in YepCode team variables as STRIPE_WEBHOOK_SECRET or any other name you prefer.

Signature Verification Implementation

const crypto = require("crypto");
const {
context: { request },
} = yepcode;
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
// Get the Stripe signature from headers
const stripeSignature = request.headers["stripe-signature"];
if (!stripeSignature) {
return {
status: 400,
body: { error: "Missing Stripe signature header" },
};
}
// Verify the webhook signature
try {
// Parse the signature header (format: t=timestamp,v1=signature)
const signatures = stripeSignature.split(",").reduce((acc, pair) => {
const [key, value] = pair.split("=");
acc[key] = value;
return acc;
}, {});
const timestamp = signatures.t;
const receivedSignature = signatures.v1;
if (!timestamp || !receivedSignature) {
console.error("Missing timestamp or signature in Stripe header");
return {
status: 401,
body: { error: "Invalid signature format" },
};
}
// Check timestamp to prevent replay attacks (allow up to 5 minutes)
const currentTime = Math.floor(Date.now() / 1000);
const webhookTime = parseInt(timestamp);
const tolerance = 300; // 5 minutes in seconds
if (Math.abs(currentTime - webhookTime) > tolerance) {
console.error("Webhook timestamp too old, possible replay attack");
return {
status: 401,
body: { error: "Request timestamp too old" },
};
}
// Create the signed payload (timestamp + raw body)
const signedPayload = timestamp + "." + request.rawBody;
const expectedSignature = crypto
.createHmac("sha256", webhookSecret)
.update(signedPayload)
.digest("hex");
if (receivedSignature !== expectedSignature) {
console.error("Stripe webhook signature verification failed");
return {
status: 401,
body: { error: "Invalid signature" },
};
}
console.log("✅ Stripe webhook signature verified successfully");
// Parse the webhook payload
const event = JSON.parse(request.rawBody);
console.log(`Processing event: ${event.type}`);
// Your business logic here
return {
status: 200,
body: { received: true },
};
} catch (error) {
console.error("Error processing Stripe webhook:", error);
return {
status: 500,
body: { error: "Internal server error" },
};
}

Testing Your Webhook

Use the Stripe CLI to test your webhook:

  1. Log in to Stripe
stripe login
  1. Forward events to your YepCode webhook. This will listen for stripe events in your current terminal.
stripe listen --forward-to https://cloud.yepcode.io/api/<your-team>/webhooks/<your-process-slug>
  1. Copy stripe listen output signing secret to your YepCode team variable STRIPE_WEBHOOK_SECRET.

  2. Open a new terminal and trigger a test event

stripe trigger payment_intent.succeeded
  1. Check your YepCode execution logs to see the signature verified and event received.

Key Points

  • ✅ Use request raw body for signature verification
  • ✅ Include timestamp validation to prevent replay attacks
  • ✅ Store webhook secrets in YepCode team variables
  • ✅ Return proper HTTP status codes (200, 401, 500)
  • ✅ Handle exceptions gracefully

The raw body signature verification ensures that webhooks actually come from Stripe, providing a secure foundation for your payment processing workflows.

Learn More