Create a webhook endpoint
Set up an HTTPS endpoint function that can accept webhook requests with a GET and a POST method. If you’re still developing your endpoint function on your local machine, it can use HTTP. After it’s publicly accessible, your webhook endpoint function must use HTTPS.
Set up your endpoint function:
Handles POST requests with a JSON payload consisting of an event object, and returns a successful status code (2xx).
Handles GET requests with a successful status code (200).
Example
Server implements a webhook endpoint that supports GET and POST methods with signature verification. Below are some sample code snippets in Python/Go/Node.
This code snippet includes a webhook function configured to check that the event type was received, to handle the event, and return a 200 response.
Python
Python | Copy |
|---|---|
#! /usr/bin/env python3.6 |
# Python 3.6 or newer required.
import hashlib import hmac import json import os import uvicorn
from typing import Dict, Any
from fastapi import FastAPI, Request, HTTPException from fastapi.responses import JSONResponse
# Replace this endpoint secret with your webhook's secret. # You can find it in your webhook settings.
SECRET = 'your_secret_here' app = FastAPI()
->
def verify_signature(signature: str, timestamp: str, body: bytes) bool:
if not signature or not timestamp or not body: return False
# Calculate expected signature expected_signature = hmac.new( SECRET.encode('utf-8'),
body + b'.' + timestamp.encode('utf-8'), hashlib.sha256
).hexdigest()
# Compare signatures using constant-time comparison return hmac.compare_digest(signature, expected_signature)
@app.get("/webhook")
async def verify_webhook():
return "Webhook endpoint is working"
@app.post("/webhook")
async def handle_webhook(request: Request): # Get signature from headers
=
=
=
signature timestamp request_id
request.headers.get('x-webhook-signature') request.headers.get("x-webhook-timestamp") request.headers.get("x-webhook-request-id")
# Get raw body
body = await request.body()
# Verify signature
if not verify_signature(signature, timestamp, body): raise HTTPException(status_code=401, detail="Invalid
signature")
try:
# Handle the event
event = json.loads(body)
for data in event.get('data', []): event_type = data.get('event_type')
if event_type == "execution.completed": print(f"Received execution completed
event({request_id}): {data.get('payload')}")
elif event_type == "execution.cancelled": print(f"Received execution cancelled
event({request_id}): {data.get('payload')}")
elif event_type == "execution.failed":
print(f"Received execution failed event({request_id}):
{data.get('payload')}")
else:
print(f"Received webhook event({request_id}):
{json.dumps(event)}")
# Return success response return {"status": "success"}
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON payload")
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception): print(f'Error: {exc}')
return JSONResponse( status_code=400, content={"error": "Bad request"}
)
if name == ' main ': port = 8083
print(f'Starting server on port {port}...') uvicorn.run(app, host="0.0.0.0", port=port)
Go
Go | Copy |
|---|---|
import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io" "log" "net/http" ) // Replace this endpoint secret with your webhook's secret. // You can find it in your webhook settings. const SECRET string = "your_secret_here" type WebhookEvent[T any] struct { EventId string `json:"id"` OrganizationId string `json:"organization_id"` Data []T `json:"data"` Timestamp string `json:"timestamp"` } type ExecutionEventData struct { EventType string `json:"event_type"` Payload ExecutionEventPayload `json:"payload"` } type ExecutionEventPayload struct { WorkflowId string `json:"workflow_id"` ExecutionId string `json:"execution_id"` FollowUpExecutionIds []string `json:"follow_up_execution_ids"` } func main() { http.HandleFunc("/webhook", webhookHandler) addr := "localhost:8081" log.Printf("Starting server on %s", addr) log.Fatal(http.ListenAndServe(addr, nil)) } |
func webhookHandler(w http.ResponseWriter, r *http.Request) { switch r.Method {
case http.MethodGet: handleGet(w, r)
case http.MethodPost: handlePost(w, r)
default:
http .Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func handleGet(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Webhook endpoint is working")
}
func handlePost(w http.ResponseWriter, r *http.Request) {
// Read the body
:=
!=
body, err
if err
io.ReadAll(r.Body) nil {
log .Printf("Failed to read request: %v", err) http .Error(w, "Error reading request body",
http.StatusBadRequest) return
}
defer r.Body.Close()
// Verify signature
:=
:=
:=
signature timestamp requestId
r.Header.Get("x-webhook-signature") r.Header.Get("x-webhook-timestamp") r.Header.Get("x-webhook-request-id")
if !verifySignature(signature, timestamp, body) {
http .Error(w, "Invalid signature", http.StatusUnauthorized) return
}
// Parse and print the payload
var event WebhookEvent[ExecutionEventData]
:=
!=
if err
json.Unmarshal(body, &event); err
nil {
log .Printf("Failed to unmarshal request: %v", err)
http .Error(w, "Invalid JSON payload", http.StatusBadRequest) return
}
// Handle the event
for _, data := range event.Data { switch data.EventType {
case "execution.completed": log .Printf(
"Received execution completed event(%s): %+v", requestId ,
data .Payload,
)
case "execution.failed": log .Printf(
"Received execution failed event(%s): %+v", requestId ,
data .Payload,
)
case "execution.cancelled": log .Printf(
"Received execution cancelled event(%s): %+v", requestId ,
data .Payload,
)
default:
log .Printf("Received webhook event(%s): %+v", requestId, event)
}
}
// Send success response w.WriteHeader(http.StatusOK)
}
func verifySignature(signature, timestamp string, body []byte) bool { if len(signature) == 0 || len(timestamp) == 0 || len(body) == 0 {
return false
}
// Calculate expected signature
mac := hmac.New(sha256.New, []byte(SECRET)) mac.Write([]byte(fmt.Sprintf("%s.%s", body, timestamp))) expectedMAC := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedMAC))
}
Node
Node-repl | Copy |
|---|---|
const crypto = require('crypto'); const express = require('express'); const app = express(); // Replace this endpoint secret with your webhook's secret. // You can find it in your webhook settings. const SECRET = 'your_secret_here'; const PORT = 8082; // Middleware to parse JSON bodies app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } })); // Verify webhook signature const verifySignature = (signature, timestamp, rawBody) => { if (!signature||!timestamp||!rawBody) return false; const expectedSignature = crypto .createHmac('sha256', SECRET) .update(`${rawBody}.${timestamp}`) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); }; // GET endpoint for health check app.get('/webhook', (req, res) => { // Return a 200 response to indicate webhook is working res.status(200).send('Webhook endpoint is working'); }); // POST endpoint for webhook event handling app.post('/webhook', (req, res) => { const signature = req.headers['x-webhook-signature']; const timestamp = req.headers['x-webhook-timestamp']; |
const requestId = req.headers['x-webhook-request-id'];
// Verify signature
if (!verifySignature(signature, timestamp, req.rawBody)) { console.log(`Event(${requestId}) signature verification failed.`) return res.status(401).json({ error: 'Invalid signature' });
}
// Handle the event let event = req.body;
event.data.forEach(item => { switch (item.event_type) {
case 'execution.completed': console.log(`Received execution completed
event(${requestId}):`, item.payload); break;
case 'execution.failed':
console.log(`Received execution failed event(${requestId}):`, item.payload);
break;
case 'execution.cancelled': console.log(`Received execution cancelled
event(${requestId}):`, item.payload); break;
default:
console.log(`Received webhook event(${requestId}):`, event);
}
});
// Return a 200 response to acknowledge receipt of the event res.status(200).json({ status: 'success' });
});
// Error handling middleware app.use((err, req, res, next) => {
console.error(err);
res.status(400).json({ error: 'Bad request' });
});
// Start server app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});