Skip to main content

Uniphore Help Center Portal

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}`);

});