Webhook Code Examples

Complete guide to webhooks, debugging, and real-time event monitoring with HookMetry

Integration Code Examples

JS
Node.js / Express — Stripe Webhooks

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'));

PY
Python / Flask — GitHub Webhooks

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
PHP — Generic HMAC Webhook

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:

  1. Create an endpoint in HookMetry with your chosen validation type
  2. Copy the generated webhook URL
  3. Send test requests using cURL or your application
  4. View real-time logs, validation results, and full payloads
  5. Replay failed requests to debug issues

Was this page helpful?

Your feedback helps us improve the docs.