Skip to main content

How Webhooks Work

Configure webhook endpoint in ABV

Create a webhook in the ABV dashboard specifying where to send prompt change notifications.Navigate to Prompts → Webhooks:Webhooks pageClick Create Webhook:Create webhook buttonChoose integration type:
  • Slack Message: OAuth-based Slack integration (zero infrastructure required)
  • Webhook Call: Custom HTTPS endpoint for any system integration
You configure the endpoint URL, headers, event filters, and prompt filters.

ABV sends POST requests on prompt events

When a selected event occurs (prompt created, updated, or deleted), ABV sends an HTTP POST request to your endpoint with full prompt data.Event types:
  • Created: New prompt version is created
  • Updated: Prompt labels or tags change (two events: one for version gaining label, one for version losing it)
  • Deleted: Prompt version is removed
ABV automatically handles retries with exponential backoff if your endpoint returns non-2xx status codes.

Receive and validate payload

Your endpoint receives a JSON payload containing event metadata and complete prompt data.Sample payload:
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2024-07-10T10:30:00Z",
  "type": "prompt-version",
  "apiVersion": "v1",
  "action": "created",
  "prompt": {
    "id": "prompt_abc123",
    "name": "movie-critic",
    "version": 3,
    "projectId": "xyz789",
    "labels": ["production", "latest"],
    "prompt": "As a {{criticLevel}} movie critic, rate {{movie}} out of 10.",
    "type": "text",
    "config": {},
    "commitMessage": "Improved critic persona",
    "tags": ["entertainment"],
    "createdAt": "2024-07-10T10:30:00Z",
    "updatedAt": "2024-07-10T10:30:00Z"
  }
}
Verify authenticity using the HMAC SHA-256 signature in the x-abv-signature header to ensure the request actually came from ABV.

Process events and trigger actions

Based on the payload, perform actions like:
  • Send Slack notifications to relevant channels
  • Trigger CI/CD pipelines (GitHub Actions, Jenkins, etc.)
  • Update external documentation systems
  • Sync prompt catalogs to databases or search indices
  • Alert monitoring systems (PagerDuty, Datadog, etc.)
  • Log changes to audit systems
Example: Slack notificationSlack messageTeams see prompt changes in real-time without leaving their communication tools.

Integration Patterns

The simplest integration: send notifications directly to Slack channels via OAuth, with no custom code or infrastructure required.How it works:
  1. ABV connects to your Slack workspace via OAuth
  2. You select target channels for notifications
  3. ABV sends formatted messages when prompts change
  4. Team sees updates in real-time
Benefits:
  • No infrastructure to host or maintain
  • No code to write
  • OAuth-secured connection
  • Rich message formatting with prompt details
Best for: Teams using Slack who want immediate visibility into prompt changes without technical overhead.Setup: See Slack Integration Setup below.
Send webhook payloads to any HTTPS endpoint for custom integrations with your existing tools and workflows.How it works:
  1. Deploy a webhook receiver (FastAPI, Express, Cloudflare Workers, etc.)
  2. Configure ABV to POST to your endpoint
  3. Process payloads and trigger custom logic
  4. Return 2xx status to acknowledge receipt
Use cases:
  • Sync prompts to external databases or search indices
  • Trigger CI/CD pipelines (GitHub Actions, Jenkins, GitLab CI)
  • Send notifications to Microsoft Teams, Discord, or custom chat platforms
  • Update documentation systems (Notion, Confluence, internal wikis)
  • Log changes to audit systems for compliance
  • Alert monitoring tools (PagerDuty, Datadog, New Relic)
Best for: Teams needing custom integrations beyond Slack, or requiring complex processing logic.Setup: See Custom Webhook Setup below.
Verify that webhook requests actually come from ABV and haven’t been tampered with using HMAC SHA-256 signatures.Why verify signatures:
  • Prevent unauthorized requests from malicious actors
  • Ensure payload integrity (data not modified in transit)
  • Production security requirement for sensitive systems
How it works:
  1. ABV generates a signing secret when you create the webhook
  2. ABV signs each request with HMAC SHA-256 using the secret
  3. Signature included in x-abv-signature header (format: t=timestamp,s=signature)
  4. Your endpoint recreates the signature and compares
Signature format:
x-abv-signature: t=1720701136,s=0123abcdef...
Security tip: Use constant-time comparison (hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js) to prevent timing attacks.Implementation: See Signature Verification Code below.
Reduce noise by filtering which events trigger webhooks and which prompts are monitored.Event filtering: Select which actions trigger webhooks:
  • Created: Only fire when new prompt versions are created
  • Updated: Only fire when labels or tags change
  • Deleted: Only fire when prompt versions are deleted
Default: all events selected.Prompt filtering: Optionally filter to specific prompts by:
  • Prompt name patterns (e.g., only production/* prompts)
  • Tags (e.g., only prompts tagged critical)
  • Labels (e.g., only prompts with production label)
Example use case:
  • Production monitoring: Filter to only production-labeled prompts for critical alerts
  • Team-specific notifications: Filter by tags to route notifications to relevant Slack channels
  • Reduce noise: Only notify on created events, ignore label changes
Configuration: Set filters when creating or editing webhooks in the ABV UI.
Design webhook handlers to be idempotent, as ABV retries failed webhook deliveries.Retry behavior:
  • ABV retries webhooks that return non-2xx status codes
  • Exponential backoff strategy (retries with increasing delays)
  • Continues until successful delivery or maximum retry limit
Idempotent handler design:
# Example: Use event ID to deduplicate
processed_events = set()  # In production, use Redis or database

def handle_webhook(event):
    event_id = event["id"]

    # Skip if already processed
    if event_id in processed_events:
        return {"status": "already_processed"}

    # Process event
    send_slack_notification(event)

    # Mark as processed
    processed_events.add(event_id)

    return {"status": "success"}
Best practices:
  • Use event id for deduplication
  • Store processed event IDs in Redis, database, or cache
  • Always return 2xx on success to stop retries
  • Design operations to be safely repeatable (e.g., upsert instead of insert)

Slack Integration Setup

Connect your Slack workspace to ABV via OAuth.Steps:
  1. In ABV dashboard, navigate to PromptsWebhooks
  2. Click Create Webhook
  3. Select Slack Message tab
  4. Click Authenticate with Slack Slack OAuth
  5. Slack OAuth flow opens in new window
  6. Select your Slack workspace
  7. Authorize ABV to post messages
Permissions requested:
  • chat:write: Post messages to channels
  • channels:read: List public channels
Security: ABV stores Slack OAuth tokens encrypted in the database. You can revoke access anytime via Slack’s App Management.
Choose which Slack channels receive prompt change notifications.Channel selectionSteps:
  1. After OAuth completes, you’ll see a dropdown of available channels
  2. Select one or more channels (you can create multiple webhooks for different channels)
  3. (Optional) Run a dry run to test the integration
Dry run:
  • Sends a test notification to the selected channel
  • Verifies OAuth permissions and channel access
  • Helps you preview message formatting before going live
Channel requirements:
  • ABV must be invited to private channels before they appear in the dropdown
  • Public channels appear automatically after OAuth
Select which prompt events trigger Slack notifications.Event selectionEvent types:
  • Created: New prompt version is created
  • Updated: Labels or tags change on existing version
  • Deleted: Prompt version is removed
Filtering options:
  • (Optional) Filter to specific prompts by name pattern
  • (Optional) Filter to specific tags
  • (Optional) Filter to specific labels (e.g., only production)
Example configurations:
  • Production monitoring: Only created events on production-labeled prompts
  • All changes: All events, all prompts (high-volume notifications)
  • Team-specific: Only prompts tagged with customer-support team tag
See prompt change notifications in your Slack channel.Slack notificationMessage includes:
  • Prompt name and version
  • Action performed (created, updated, deleted)
  • Labels and tags
  • Commit message (if provided)
  • Timestamp
  • Link back to ABV for full details
Message format example:
🔔 Prompt Updated

Name: movie-critic
Version: 3
Action: created
Labels: production, latest
Tags: entertainment

Commit message: "Improved critic persona"

View in ABV →
Team members can click through to ABV for full prompt content and history.

Custom Webhook Setup

Set up a custom HTTPS endpoint to receive webhook POST requests.Steps:
  1. In ABV dashboard, navigate to PromptsWebhooks
  2. Click Create Webhook
  3. Select Webhook Call tab
  4. Configure request details: Webhook configuration
URL: HTTPS endpoint that accepts POST requests
https://your-domain.com/api/webhooks/abv-prompts
Headers: Default headers are automatically included:
  • Content-Type: application/json
  • User-Agent: ABV/1.0
  • x-abv-signature: <signature> (HMAC SHA-256)
Custom headers: Add any additional headers your endpoint requires (e.g., API keys, auth tokens)
X-API-Key: your-api-key
Authorization: Bearer your-token
Event selection: Choose which events trigger the webhook (created, updated, deleted)Prompt filters: Optionally filter to specific prompts or tags
Create an endpoint that receives and processes webhook payloads.Minimal webhook handler (Python/FastAPI):
from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
from typing import Dict, Any

app = FastAPI()

class WebhookPayload(BaseModel):
    id: str
    timestamp: str
    type: str
    action: str
    prompt: Dict[str, Any]

@app.post("/api/webhooks/abv-prompts")
async def handle_abv_webhook(payload: WebhookPayload):
    """Process ABV prompt webhook."""
    print(f"Received {payload.action} event for prompt {payload.prompt['name']} v{payload.prompt['version']}")

    # Your custom logic here:
    # - Send notification to Microsoft Teams
    # - Trigger CI/CD pipeline
    # - Update documentation
    # - Log to audit system
    # etc.

    # Return 2xx to acknowledge receipt
    return {"status": "received", "event_id": payload.id}
Minimal webhook handler (Node.js/Express):
import express from 'express';

const app = express();
app.use(express.json());

app.post('/api/webhooks/abv-prompts', (req, res) => {
  const { id, action, prompt } = req.body;

  console.log(`Received ${action} event for prompt ${prompt.name} v${prompt.version}`);

  // Your custom logic here

  res.status(200).json({ status: 'received', event_id: id });
});

app.listen(3000, () => console.log('Webhook handler listening on port 3000'));
Important: Always return 2xx status codes on successful processing to prevent ABV from retrying.
Understand the structure of webhook payloads to extract relevant data.Full payload structure:
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2024-07-10T10:30:00Z",
  "type": "prompt-version",
  "apiVersion": "v1",
  "action": "created",
  "prompt": {
    "id": "prompt_abc123",
    "name": "movie-critic",
    "version": 3,
    "projectId": "xyz789",
    "labels": ["production", "latest"],
    "prompt": "As a {{criticLevel}} movie critic, rate {{movie}} out of 10.",
    "type": "text",
    "config": { "model": "gpt-4o", "temperature": 0.7 },
    "commitMessage": "Improved critic persona",
    "tags": ["entertainment"],
    "createdAt": "2024-07-10T10:30:00Z",
    "updatedAt": "2024-07-10T10:30:00Z"
  }
}
Key fields:
  • id: Unique event identifier (use for deduplication)
  • timestamp: ISO 8601 timestamp of when event occurred
  • action: Event type (created, updated, deleted)
  • prompt.name: Prompt name (may include folder path like customer-support/billing)
  • prompt.version: Version number
  • prompt.labels: Array of labels (e.g., ["production", "staging"])
  • prompt.tags: Array of tags for categorization
  • prompt.commitMessage: Optional message describing the change
  • prompt.prompt: Actual prompt content (text or chat messages array)
  • prompt.config: Model configuration (model name, temperature, etc.)
Use cases by field:
  • action == "created" && "production" in labels: Alert team of production deployment
  • tags: Route notifications to team-specific channels
  • commitMessage: Include in Slack notification for context
  • version: Track version history in external systems
Deploy your webhook handler to a publicly accessible HTTPS endpoint.Deployment options:Option 1: Serverless (Vercel, Netlify, Cloudflare Workers)
  • Zero infrastructure management
  • Automatic scaling
  • Pay-per-request pricing
  • Example: Deploy Express handler to Vercel
Option 2: Cloud Functions (AWS Lambda, Google Cloud Functions, Azure Functions)
  • Event-driven execution
  • Integrates with cloud services
  • Example: Lambda function triggered by API Gateway
Option 3: Traditional Hosting (Render, Fly.io, Heroku)
  • Full control over runtime environment
  • Persistent state (if needed)
  • Example: FastAPI app on Render.com
Requirements:
  • HTTPS endpoint (TLS/SSL required)
  • Accepts HTTP POST requests
  • Returns 2xx status on success
  • Responds within reasonable timeout (< 30 seconds recommended)
After deployment:
  1. Note your public HTTPS URL
  2. Update ABV webhook configuration with the URL
  3. Test by triggering a prompt event in ABV
  4. Check webhook handler logs for incoming requests

Signature Verification

Webhook signature verification ensures requests actually come from ABV and haven’t been tampered with.Security benefits:
  • Authentication: Confirm request originated from ABV, not a malicious actor
  • Integrity: Verify payload wasn’t modified in transit
  • Non-repudiation: Prove ABV sent the specific payload
When verification is critical:
  • Production deployments triggered by webhooks
  • Webhooks that modify databases or external systems
  • Compliance requirements (audit trails, data integrity)
  • Public endpoints accessible from the internet
How it works:
  1. ABV generates a signing secret when you create the webhook (copy and store securely)
  2. For each request, ABV computes HMAC SHA-256 of timestamp.payload using the secret
  3. Signature included in x-abv-signature header: t=<timestamp>,s=<signature>
  4. Your handler recreates the signature and compares
Signature format:
x-abv-signature: t=1720701136,s=a1b2c3d4e5f6...
  • t: Unix timestamp when signature was generated
  • s: HMAC SHA-256 hex digest
Verify webhook signatures in Python using the hmac module.Implementation:
import hmac
import hashlib
from typing import Optional


def verify_abv_signature(
    raw_body: str,
    signature_header: str,
    secret: str,
) -> bool:
    """
    Validate a ABV webhook/event signature.

    Parameters
    ----------
    raw_body : str
        The request body exactly as received (no decoding or reformatting).
    signature_header : str
        The value of the `x-abv-signature` header, e.g. "t=1720701136,s=0123abcd...".
    secret : str
        Your ABV signing secret.

    Returns
    -------
    bool
        True if the signature is valid, otherwise False.
    """
    # Split "t=timestamp,s=signature" into the two expected key/value chunks
    try:
        ts_pair, sig_pair = signature_header.split(",", 1)
    except ValueError:  # wrong format / missing comma
        return False

    # Extract values (everything after the first "=")
    if "=" not in ts_pair or "=" not in sig_pair:
        return False
    timestamp = ts_pair.split("=", 1)[1]
    received_sig_hex = sig_pair.split("=", 1)[1]

    # Recreate the message and compute the expected HMAC-SHA256 hex digest
    message = f"{timestamp}.{raw_body}".encode("utf-8")
    expected_sig_hex = hmac.new(
        secret.encode("utf-8"), message, hashlib.sha256
    ).hexdigest()

    # Use constant-time comparison on the *decoded* byte strings
    try:
        return hmac.compare_digest(
            bytes.fromhex(received_sig_hex), bytes.fromhex(expected_sig_hex)
        )
    except ValueError:  # received_sig_hex isn't valid hex
        return False
Usage in FastAPI:
from fastapi import FastAPI, Request, HTTPException, Header

WEBHOOK_SECRET = "your-signing-secret-from-abv"

@app.post("/api/webhooks/abv-prompts")
async def handle_webhook(
    request: Request,
    x_abv_signature: str = Header(...)
):
    # Read raw body (important: don't decode or parse yet)
    raw_body = await request.body()

    # Verify signature
    if not verify_abv_signature(raw_body.decode(), x_abv_signature, WEBHOOK_SECRET):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Parse JSON after verification
    payload = await request.json()

    # Process webhook...
    return {"status": "received"}
Security notes:
  • Use hmac.compare_digest() for constant-time comparison (prevents timing attacks)
  • Verify signature before parsing or processing payload
  • Store signing secret in environment variables, not code
Verify webhook signatures in Node.js using the crypto module.Implementation:
import crypto from "crypto";

export function verifyABVSignature(
  rawBody: string,
  signatureHeader: string,
  secret: string
): boolean {
  // Parse header: "t=timestamp,s=signature"
  const [tsPair, sigPair] = signatureHeader.split(",");
  if (!tsPair || !sigPair) return false;

  const timestamp = tsPair.split("=")[1];
  const receivedSig = sigPair.split("=")[1];

  // Recreate signature
  const expectedSig = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`, "utf8")
    .digest("hex");

  // Constant-time comparison
  try {
    return crypto.timingSafeEqual(
      Buffer.from(receivedSig, "hex"),
      Buffer.from(expectedSig, "hex")
    );
  } catch {
    return false;  // receivedSig not valid hex
  }
}
Usage in Express:
import express from 'express';
import { verifyABVSignature } from './verify-signature';

const WEBHOOK_SECRET = process.env.ABV_WEBHOOK_SECRET!;

const app = express();

// Use express.text() to get raw body as string
app.post('/api/webhooks/abv-prompts',
  express.text({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-abv-signature'] as string;

    // Verify signature
    if (!verifyABVSignature(req.body, signature, WEBHOOK_SECRET)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    // Parse JSON after verification
    const payload = JSON.parse(req.body);

    // Process webhook...
    res.json({ status: 'received', event_id: payload.id });
  }
);
Security notes:
  • Use crypto.timingSafeEqual() for constant-time comparison
  • Parse rawBody to string before passing to verification function
  • Verify before parsing JSON to prevent processing malicious payloads
Common issues when implementing signature verification.Issue: Signature always failsCauses and solutions:
  1. Wrong secret: Verify you copied the signing secret correctly from ABV
    • Regenerate secret in ABV and update your code
    • Check for extra whitespace or newlines
  2. Body modified before verification: Raw body must be exactly as received
    # Wrong: body already parsed
    payload = await request.json()
    verify_signature(json.dumps(payload), sig, secret)  # FAILS
    
    # Correct: use raw body
    raw_body = await request.body()
    verify_signature(raw_body.decode(), sig, secret)  # WORKS
    payload = json.loads(raw_body)
    
  3. Encoding issues: Ensure consistent UTF-8 encoding
    message = f"{timestamp}.{raw_body}".encode("utf-8")  # Explicit UTF-8
    
  4. Header parsing error: Verify header format is t=...,s=...
    # Debug: print header value
    print(f"Signature header: {signature_header}")
    
Issue: Occasional signature failuresCause: Clock skew between ABV servers and your serverSolution: Add timestamp validation with tolerance window
import time

def verify_abv_signature_with_timestamp(raw_body, signature_header, secret, tolerance=300):
    # ... existing verification code ...

    # After signature verification passes, check timestamp
    current_time = int(time.time())
    timestamp_int = int(timestamp)

    if abs(current_time - timestamp_int) > tolerance:
        # Reject if timestamp is > 5 minutes old (replay attack protection)
        return False

    return True
Issue: Constant-time comparison failsCause: Hex strings not properly converted to bytesSolution: Use bytes.fromhex() or Buffer.from(sig, 'hex')
# Correct: convert hex to bytes before comparison
return hmac.compare_digest(
    bytes.fromhex(received_sig_hex),
    bytes.fromhex(expected_sig_hex)
)

Common Use Cases

Get alerted immediately when production prompts are updated to coordinate deployments and monitor for unexpected changes.Setup:
  • Create Slack webhook filtered to production label
  • Subscribe #production-alerts channel
  • Configure to trigger on created and updated events
Workflow:
  1. Engineer updates prompt and assigns production label
  2. Slack notification sent to #production-alerts
  3. Team aware of change, can monitor for issues
  4. If problems arise, easy to correlate with prompt update
Example Slack message:
🚨 Production Prompt Updated

Name: customer-support/billing
Version: 12
Action: created
Label: production

Commit: "Updated refund policy for EU customers"

@channel - New billing prompt deployed
Team can immediately investigate if customer support tickets spike after the change.
Keep cross-functional teams informed about prompt changes that affect their work.Setup:
  • Create multiple webhooks for different teams
  • Filter by tags (e.g., team:customer-support, team:marketing)
  • Route notifications to team-specific Slack channels
Example:
  • Customer support team: Notified of prompts tagged customer-support
  • Marketing team: Notified of prompts tagged marketing
  • Engineering team: Notified of all production prompts
Benefit: Each team sees only relevant changes, reducing notification fatigue while ensuring visibility.
Trigger automated testing and deployment workflows when prompts change.Setup:
  • Configure custom webhook to trigger GitHub Actions, Jenkins, or GitLab CI
  • Run automated tests on prompt changes
  • Deploy to staging/production environments
Example workflow:
  1. Engineer updates prompt in ABV
  2. Webhook triggers GitHub Actions workflow
  3. Workflow runs integration tests with new prompt
  4. If tests pass, deploy prompt to staging
  5. Manual approval → deploy to production
Implementation: See GitHub Integration for detailed setup.
Automatically sync prompts to external databases, search indices, or documentation systems.Use cases:
  • Elasticsearch index: Keep searchable prompt catalog up-to-date
  • Documentation: Auto-update internal wiki when prompts change
  • Audit database: Log all prompt changes for compliance
  • Prompt versioning system: Sync to external version control
Example: Sync to Elasticsearch
from elasticsearch import Elasticsearch

es = Elasticsearch(["https://your-elasticsearch-cluster"])

@app.post("/api/webhooks/abv-prompts")
async def sync_to_elasticsearch(payload: WebhookPayload):
    # Index prompt in Elasticsearch
    es.index(
        index="prompts",
        id=payload.prompt["id"],
        document={
            "name": payload.prompt["name"],
            "version": payload.prompt["version"],
            "content": payload.prompt["prompt"],
            "labels": payload.prompt["labels"],
            "tags": payload.prompt["tags"],
            "updated_at": payload.timestamp
        }
    )

    return {"status": "synced"}
Now prompts are searchable in Elasticsearch, enabling advanced search capabilities.
Maintain comprehensive audit logs of all prompt changes for regulatory compliance (GDPR, HIPAA, SOC 2, etc.).Setup:
  • Configure webhook to log all events to audit database
  • Capture who made the change, what changed, when, and why (commit message)
  • Store immutable records for compliance audits
Example audit record:
{
  "event_id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2024-07-10T10:30:00Z",
  "action": "created",
  "resource_type": "prompt",
  "resource_id": "prompt_abc123",
  "resource_name": "customer-support/gdpr-request-handler",
  "version": 5,
  "labels": ["production"],
  "commit_message": "Updated GDPR data deletion flow per legal review",
  "actor": "[email protected]",
  "changes": {
    "before": {...},
    "after": {...}
  }
}
Compliance value: Demonstrates change management controls, supports audit requirements, proves data governance.

Next Steps