Complete guide to webhooks, debugging, and real-time event monitoring with HookMetry
Uses the official Stripe SDK. The SDK handles raw body verification internally — you just need to pass the correct Buffer.
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
// ✅ express.raw() ONLY for the webhook route — not globally
// express.json() globally would consume the raw stream Stripe needs to verify
app.post(
'/webhooks/stripe',
express.raw({ type: 'application/json' }),
async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
// req.body must be the raw Buffer — Stripe SDK verifies the signature internally
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET // whsec_...
);
} catch (err) {
console.error('Webhook error:', err.message);
return res.status(400).send('Webhook Error: ' + err.message);
}
// Handle different event types
switch (event.type) {
case 'payment_intent.succeeded':
await fulfillOrder(event.data.object);
break;
case 'payment_intent.payment_failed':
await notifyPaymentFailed(event.data.object);
break;
case 'customer.subscription.deleted':
await cancelSubscription(event.data.object);
break;
default:
console.log('Unhandled event type:', event.type);
}
// Always return 200 — Stripe will retry on non-2xx
res.status(200).json({ received: true });
}
);
app.listen(3000, () => console.log('Server running on port 3000'));Uses request.get_data() to get raw bytes. Never call request.json before verifying the signature.
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/webhooks/github', methods=['POST'])
def github_webhook():
sig_header = request.headers.get('X-Hub-Signature-256', '')
secret = os.environ['GITHUB_WEBHOOK_SECRET']
# raw bytes — MUST call get_data() before any JSON parsing
raw_body = request.get_data()
# Verify signature
expected_sig = 'sha256=' + hmac.new(
secret.encode('utf-8'), raw_body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected_sig, sig_header):
abort(401, 'Invalid signature')
event_type = request.headers.get('X-GitHub-Event')
payload = request.get_json(force=True) # safe to parse now
if event_type == 'ping':
return {'pong': True}, 200
if event_type == 'push':
branch = payload.get('ref', '').replace('refs/heads/', '')
commits = len(payload.get('commits', []))
print(f'{commits} commits pushed to {branch}')
elif event_type == 'pull_request':
action = payload.get('action')
pr_number = payload['pull_request']['number']
print(f'PR #{pr_number} was {action}')
else:
print(f'Unhandled event: {event_type}')
return {'status': 'ok'}, 200
if __name__ == '__main__':
app.run(port=5000)PHP's php://input reads the raw request body before any framework parsing.
<?php
// webhook.php
header('Content-Type: application/json');
// Get raw POST data
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);
// Get signature from headers
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$secret = getenv('WEBHOOK_SECRET');
// Verify HMAC signature
if (!verifySignature($payload, $signature, $secret)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
// Process webhook
$eventType = $data['type'] ?? 'unknown';
switch ($eventType) {
case 'order.created':
handleNewOrder($data);
break;
case 'order.completed':
handleCompletedOrder($data);
break;
default:
error_log("Unhandled event: " . $eventType);
}
http_response_code(200);
echo json_encode(['status' => 'success']);
function verifySignature($payload, $signature, $secret) {
$expected = hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}
function handleNewOrder($data) {
// Your business logic
error_log("New order: " . $data['id']);
}
function handleCompletedOrder($data) {
// Your business logic
error_log("Completed order: " . $data['id']);
}
?>Testing Tips:
Use HookMetry to test these implementations without modifying your provider settings:
Was this page helpful?
Your feedback helps us improve the docs.