Implementation

Code examples and best practices for implementing webhook handlers.

Framework Examples

Node.js (Express)

import express from 'express';
import crypto from 'crypto';

const app = express();

// Capture raw body for signature verification
app.use(express.json({
  verify: (req: any, _res, buf) => { req.rawBody = buf; }
}));

function isValidSignature(rawBody: Buffer, signature: string, secret: string) {
  const computed = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return (
    computed.length === (signature || '').length &&
    crypto.timingSafeEqual(Buffer.from(computed, 'hex'), Buffer.from(signature || '', 'hex'))
  );
}

app.post('/webhooks', async (req: any, res) => {
  const signature = req.header('X-Zellify-Signature') || '';
  const secret = process.env.ZELLIFY_WEBHOOK_SECRET || '';

  if (!isValidSignature(req.rawBody, signature, secret)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = req.body;

  // Acknowledge quickly
  res.status(200).json({ received: true });

  // Process asynchronously
  process.nextTick(async () => {
    try {
      await WebhookHandler.processEvent(event);
    } catch (err) {
      console.error('Webhook processing error:', err);
    }
  });
});

app.listen(3000, () => console.log('Webhook server running on 3000'));

Python (Flask)

from flask import Flask, request, jsonify
import hmac, hashlib, os, threading

app = Flask(__name__)

def valid_signature(payload: bytes, signature: str, secret: str) -> bool:
    computed = hmac.new(secret.encode('utf-8'), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, signature or '')

@app.route('/webhooks', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Zellify-Signature', '')
    payload = request.get_data()  # raw bytes
    secret = os.environ.get('ZELLIFY_WEBHOOK_SECRET', '')

    if not valid_signature(payload, signature, secret):
        return jsonify({'error': 'Invalid signature'}), 401

    event = request.json

    # Acknowledge quickly
    response = jsonify({'received': True})

    # Process in background
    threading.Thread(target=WebhookHandler.process_event, args=(event,)).start()

    return response, 200

PHP (Laravel-style controller)

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class WebhookController extends Controller
{
    public function handle(Request $request)
    {
        $payload = $request->getContent();
        $signature = $request->header('X-Zellify-Signature', '');
        $secret = env('ZELLIFY_WEBHOOK_SECRET', '');

        $computed = hash_hmac('sha256', $payload, $secret);
        if (!hash_equals($computed, $signature)) {
            return response()->json(['error' => 'Invalid signature'], 401);
        }

        // Ack early
        $response = response()->json(['received' => true]);

        // Process in background (queue preferred)
        $event = json_decode($payload, true);
        dispatch(function () use ($event) {
            WebhookHandler::processEvent($event);
        });

        return $response;
    }
}

Java (Spring Boot)

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

@RestController
public class WebhookController {
  @PostMapping("/webhooks")
  public ResponseEntity<?> handleWebhook(@RequestHeader("X-Zellify-Signature") String signature,
                                         @RequestBody String payload) {
    try {
      String secret = System.getenv("ZELLIFY_WEBHOOK_SECRET");
      Mac hmac = Mac.getInstance("HmacSHA256");
      hmac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
      byte[] computed = hmac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
      String computedHex = bytesToHex(computed);

      if (!MessageDigest.isEqual(computedHex.getBytes(StandardCharsets.UTF_8), signature.getBytes(StandardCharsets.UTF_8))) {
        return ResponseEntity.status(401).body("Invalid signature");
      }

      // Ack early
      ResponseEntity<?> ack = ResponseEntity.ok("{\"received\":true}");

      // Async processing
      new Thread(() -> {
        try {
          WebhookHandler.processEvent(payload);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }).start();

      return ack;
    } catch (Exception e) {
      return ResponseEntity.status(500).body(e.getMessage());
    }
  }

  private static String bytesToHex(byte[] bytes) {
    StringBuilder hex = new StringBuilder();
    for (byte b : bytes) {
      String h = Integer.toHexString(0xff & b);
      if (h.length() == 1) hex.append('0');
      hex.append(h);
    }
    return hex.toString();
  }
}

Go (net/http)

package main

import (
  "crypto/hmac"
  "crypto/sha256"
  "crypto/subtle"
  "encoding/hex"
  "encoding/json"
  "io"
  "log"
  "net/http"
  "os"
)

type resp struct { Received bool `json:"received"`; Error string `json:"error,omitempty"` }

func handleWebhook(w http.ResponseWriter, r *http.Request) {
  sig := r.Header.Get("X-Zellify-Signature")
  body, _ := io.ReadAll(r.Body)
  secret := os.Getenv("ZELLIFY_WEBHOOK_SECRET")

  mac := hmac.New(sha256.New, []byte(secret))
  mac.Write(body)
  computed := hex.EncodeToString(mac.Sum(nil))

  if subtle.ConstantTimeCompare([]byte(computed), []byte(sig)) != 1 {
    w.WriteHeader(http.StatusUnauthorized)
    json.NewEncoder(w).Encode(resp{Error: "Invalid signature"})
    return
  }

  // Ack early
  w.WriteHeader(http.StatusOK)
  json.NewEncoder(w).Encode(resp{Received: true})

  // Async processing
  go func(b []byte) {
    var event map[string]any
    if err := json.Unmarshal(b, &event); err != nil {
      log.Println("Invalid JSON:", err)
      return
    }
    if err := processEvent(event); err != nil { log.Println("Webhook error:", err) }
  }(body)
}

func main() {
  http.HandleFunc("/webhooks", handleWebhook)
  log.Fatal(http.ListenAndServe(":3000", nil))
}

Last updated