Handler examples
Webhook handler code samples for popular languages and frameworks.
Here are examples of webhook handlers for different languages and frameworks.
Best practices
Section titled “Best practices”When implementing webhook handlers, you’ll want to:
- Validate the webhook signature immediately (see Security)
- Return a quick 200 OK response to acknowledge receipt
- Move the actual processing to a background task/job
- Handle different event types based on the webhook topic
- Add error handling and logging for debugging issues
Choose the example that matches your tech stack and adapt it to your needs.
Python (FastAPI)
Section titled “Python (FastAPI)”from fastapi import FastAPI, Request, HTTPExceptionimport 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 topic if topic.startswith('conversation.'): await handle_conversation_event(topic, 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)
Section titled “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; case "conversation.user.created": case "conversation.user.replied": await handleConversationUserMessage(data); break; case "conversation.admin.replied": await handleConversationAdminReply(data); break; default: if (topic.startsWith("conversation.")) { await handleConversationEvent(topic, data); } else { 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)
Section titled “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(true) { $topic === 'post.created' => $this->handlePostCreated($payload), $topic === 'post.updated' => $this->handlePostUpdated($payload), str_starts_with($topic, 'conversation.') => $this->handleConversationEvent($topic, $payload), default => Log::warning("Unhandled webhook topic: {$topic}") };
} catch (\Exception $e) { Log::error('Error processing webhook: ' . $e->getMessage()); } }}Ruby (Rails)
Section titled “Ruby (Rails)”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) when /\Aconversation\./ handle_conversation_event(topic, 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: {}) endendJava (Spring Boot)
Section titled “Java (Spring Boot)”@RestController@Slf4jpublic 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 { String topic = payload.getTopic(); if (topic.startsWith("conversation.")) { handleConversationEvent(topic, payload); } else { switch (topic) { case "post.created": handlePostCreated(payload); break; case "post.updated": handlePostUpdated(payload); break; default: log.warn("Unhandled webhook topic: {}", topic); } } } catch (Exception e) { log.error("Error processing webhook", e); } }}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 { case payload.Topic == "post.created": h.handlePostCreated(payload) case payload.Topic == "post.updated": h.handlePostUpdated(payload) case strings.HasPrefix(payload.Topic, "conversation."): h.handleConversationEvent(payload) default: h.logger.Printf("Unhandled webhook topic: %s", payload.Topic) }}C# (.NET)
Section titled “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 { if (payload.Topic.StartsWith("conversation.")) { await HandleConversationEventAsync(payload); } else { 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"); } }}