Skip to content
Last updated

Webhook handler examples

Here are examples of webhook handlers for different languages and frameworks.

Best practices

When implementing webhook handlers, you'll want to:

  1. Validate the webhook signature immediately (see Security page)
  2. Return a quick 200 OK response to acknowledge receipt
  3. Move the actual processing to a background task/job
  4. Handle different event types based on the webhook topic
  5. Add error handling and logging for debugging issues

Choose the example that matches your tech stack and adapt it to your needs.


Python (FastAPI)

from fastapi import FastAPI, Request, HTTPException
import logging

app = FastAPI()
logger = logging.getLogger(__name__)

async def process_webhook_event(event_data: dict) -> None:
    """Process the webhook event asynchronously."""
    try:
        topic = event_data.get('topic')

        match topic:
            case 'post.created':
                await handle_post_created(event_data)
            case 'post.updated':
                await handle_post_updated(event_data)
            case _:
                logger.warning(f"Unhandled webhook topic: {topic}")

    except Exception as e:
        logger.error(f"Error processing webhook: {str(e)}", exc_info=True)

@app.post("/webhooks/featurebase")
async def handle_webhook(request: Request):
    try:
        # Return 200 immediately to acknowledge receipt
        background_tasks = request.background

        # Get the raw payload
        payload = await request.json()

        # Verify webhook signature (implemented in security section)
        # verify_webhook_signature(request)

        # Process webhook asynchronously
        background_tasks.add_task(process_webhook_event, payload)

        return {"status": "accepted"}

    except Exception as e:
        logger.error(f"Webhook handler error: {str(e)}", exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")

JavaScript (Express)

const express = require("express");
const router = express.Router();

async function processWebhook(payload) {
  const { topic, data } = payload;

  try {
    switch (topic) {
      case "post.created":
        await handlePostCreated(data);
        break;
      case "post.updated":
        await handlePostUpdated(data);
        break;
      default:
        console.warn(`Unhandled webhook topic: ${topic}`);
    }
  } catch (error) {
    console.error("Error processing webhook:", error);
  }
}

router.post("/webhooks/featurebase", async (req, res) => {
  try {
    // Return 200 immediately to acknowledge receipt
    res.status(200).json({ status: "accepted" });

    // Verify webhook signature (implemented in security section)
    // await verifyWebhookSignature(req);

    // Process webhook asynchronously
    processWebhook(req.body).catch((err) => {
      console.error("Webhook processing error:", err);
    });
  } catch (error) {
    console.error("Webhook handler error:", error);
  }
});

module.exports = router;

PHP (Laravel)

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class WebhookController extends Controller
{
    public function handle(Request $request)
    {
        try {
            // Return 200 immediately to acknowledge receipt
            response()->json(['status' => 'accepted'], 200)->send();

            // Verify webhook signature (implemented in security section)
            // $this->verifyWebhookSignature($request);

            // Process webhook asynchronously
            $this->processWebhook($request->all());

        } catch (\Exception $e) {
            Log::error('Webhook handler error: ' . $e->getMessage());
        }
    }

    private function processWebhook(array $payload)
    {
        try {
            $topic = $payload['topic'] ?? null;

            match($topic) {
                'post.created' => $this->handlePostCreated($payload),
                'post.updated' => $this->handlePostUpdated($payload),
                default => Log::warning("Unhandled webhook topic: {$topic}")
            };

        } catch (\Exception $e) {
            Log::error('Error processing webhook: ' . $e->getMessage());
        }
    }
}

Ruby (Rails)

# app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def handle
    # Return 200 immediately to acknowledge receipt
    render json: { status: 'accepted' }, status: :ok

    # Verify webhook signature (implemented in security section)
    # verify_webhook_signature!

    # Process asynchronously
    process_webhook(webhook_params.to_h)

  rescue StandardError => e
    Rails.logger.error "Webhook handler error: #{e.message}"
  end

  private

  def process_webhook(payload)
    topic = payload['topic']

    case topic
    when 'post.created'
      handle_post_created(payload)
    when 'post.updated'
      handle_post_updated(payload)
    else
      Rails.logger.warn "Unhandled webhook topic: #{topic}"
    end

  rescue StandardError => e
    Rails.logger.error "Error processing webhook: #{e.message}"
  end

  def webhook_params
    params.permit(:topic, :id, data: {})
  end
end

Java (Spring Boot)

@RestController
@Slf4j
public class WebhookController {

    @PostMapping("/webhooks/featurebase")
    public ResponseEntity<WebhookResponse> handleWebhook(
        @RequestBody WebhookPayload payload
    ) {
        try {
            // Return 200 immediately to acknowledge receipt
            ResponseEntity<WebhookResponse> response =
                ResponseEntity.ok(new WebhookResponse("accepted"));

            // Verify webhook signature (implemented in security section)
            // verifyWebhookSignature(request);

            // Process asynchronously
            CompletableFuture.runAsync(() -> processWebhook(payload));

            return response;

        } catch (Exception e) {
            log.error("Webhook handler error", e);
            return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .build();
        }
    }

    private void processWebhook(WebhookPayload payload) {
        try {
            switch (payload.getTopic()) {
                case "post.created":
                    handlePostCreated(payload);
                    break;
                case "post.updated":
                    handlePostUpdated(payload);
                    break;
                default:
                    log.warn("Unhandled webhook topic: {}",
                        payload.getTopic());
            }
        } catch (Exception e) {
            log.error("Error processing webhook", e);
        }
    }
}

Go

package webhooks

import (
    "encoding/json"
    "log"
    "net/http"
)

type WebhookHandler struct {
    logger *log.Logger
}

func NewWebhookHandler(logger *log.Logger) *WebhookHandler {
    return &WebhookHandler{logger: logger}
}

func (h *WebhookHandler) HandleWebhook(w http.ResponseWriter, r *http.Request) {
    // Return 200 immediately to acknowledge receipt
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(map[string]string{"status": "accepted"})

    // Verify webhook signature (implemented in security section)
    // if err := verifyWebhookSignature(r); err != nil {
    //     h.logger.Printf("Invalid webhook signature: %v", err)
    //     return
    // }

    // Parse payload
    var payload WebhookPayload
    if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
        h.logger.Printf("Error decoding webhook: %v", err)
        return
    }

    // Process asynchronously
    go h.processWebhook(payload)
}

func (h *WebhookHandler) processWebhook(payload WebhookPayload) {
    switch payload.Topic {
    case "post.created":
        h.handlePostCreated(payload)
    case "post.updated":
        h.handlePostUpdated(payload)
    default:
        h.logger.Printf("Unhandled webhook topic: %s", payload.Topic)
    }
}

C# (.NET)

[ApiController]
[Route("webhooks")]
public class WebhookController : ControllerBase
{
    private readonly ILogger<WebhookController> _logger;

    public WebhookController(ILogger<WebhookController> logger)
    {
        _logger = logger;
    }

    [HttpPost("featurebase")]
    public IActionResult HandleWebhook([FromBody] WebhookPayload payload)
    {
        try
        {
            // Verify webhook signature (implemented in security section)
            // await VerifyWebhookSignatureAsync(Request);

            // Process asynchronously
            _ = Task.Run(() => ProcessWebhookAsync(payload));

            return Ok(new { status = "accepted" });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error handling webhook");
            return StatusCode(500);
        }
    }

    private async Task ProcessWebhookAsync(WebhookPayload payload)
    {
        try
        {
            switch (payload.Topic)
            {
                case "post.created":
                    await HandlePostCreatedAsync(payload);
                    break;
                case "post.updated":
                    await HandlePostUpdatedAsync(payload);
                    break;
                default:
                    _logger.LogWarning(
                        "Unhandled webhook topic: {Topic}",
                        payload.Topic);
                    break;
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing webhook");
        }
    }
}