Here are examples of webhook handlers for different languages and frameworks.
When implementing webhook handlers, you'll want to:
- Validate the webhook signature immediately (see Security page)
- 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.
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")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
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());
}
}
}# 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@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);
}
}
}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)
}
}[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");
}
}
}