Skip to main content

What are webhooks?

Webhooks are HTTP callbacks that deliver data to external systems in real-time when an event occurs within Infrahub. They allow you to:

  • Notify external systems about changes in your infrastructure (for example new configuration generated)
  • Trigger automated workflows in CI/CD pipelines
  • Keep other tools and services synchronized with your infrastructure data
  • Build custom integrations with your existing toolchain
info

The Webhook System also powers the Activity Log.

Configuring webhooks

Infrahub supports two different types of webhooks:

  • Standard Webhook sends event information to an external endpoint.
  • Custom Webhook allows the ability to tie events to a Transformation to configure the payload being sent to an external endpoint.
note

Custom webhooks might be useful in situations where the endpoint needs a specific payload to be successful. A use-case that might need this would be triggering external actions in platforms like GitHub and GitLab.

The data passed into a normal Python Transformation will typically be the result of the GraphQL query, but webhooks execute the Transformation by passing in the webhook data instead.

Currently, it is required to define a GraphQL query to be defined on a Transformation regardless of whether it is used for a custom webhook or not. In the future, this will not be required.

Webhooks can be configured to trigger on specific event types, such as:

  • infrahub.branch.merged
  • infrahub.node.updated

This allows you to tailor webhooks for specific use cases.

Configuring branch types

Webhooks can also be configured to trigger based on branch types:

  • Other Branches – All branches except the default branch.
  • Default Branch – Only the default branch.
  • All Branches – Every branch.

This flexibility ensures that webhooks only trigger when relevant, depending on the use case. For example, you might configure a webhook to notify an external system only when a user makes changes directly to the default branch.

Node kind

Node kinds can be configured to only trigger webhooks for specific nodes. For example only send webhooks for the node kind of InfraDevice.

note

Node kind can only be used where node_kind is True in Infrahub events. Below are some example events that are enabled:

  • infrahub.node.created
  • infrahub.node.updated
  • infrahub.node.deleted
  • infrahub.artifact.created
  • infrahub.artifact.deleted
  • infrahub.group.member_added
  • infrahub.group.member_removed
  • infrahub.validator.member_removed

Webhook configuration details

  • Description: A description can be set to identify the webhooks purpose.
  • URL: The destination URL can be configured, such as http://ansible-eda:8080.
  • Transformation (Custom Webhook only): The Python Transformation used to customize the webhook payload before being sent to the webhook receiver.

Shared key for security

A shared key can be configured to sign webhook requests. This key ensures the authenticity and integrity of the webhook message.

When a webhook is sent:

  1. The sender generates a signature using the shared key.
  2. The signature is included in the request headers (webhook-signature).
  3. The message ID is included in the request headers (webhook-id).
  4. The timestamp is included in the request headers (webhook-timestamp).

The receiver then uses the same shared key to verify the signature, confirming that the message has not been tampered with and that it is from a trusted source.

Python FastAPI Example
webhook-reciever.py
import base64
import hashlib
import hmac
import json

from fastapi import FastAPI, Request, HTTPException
import uvicorn


app = FastAPI()
SHARED_KEY = b"supersecretkey"


def verify_signature(message_id, timestamp, payload, received_signature):
data = f"{message_id}.{timestamp}.{payload}".encode()
expected_signature = base64.b64encode(
hmac.new(SHARED_KEY, data, hashlib.sha256).digest()
).decode("utf-8")
return hmac.compare_digest(f"v1,{expected_signature}", received_signature)


@app.get("/")
async def health_check():
return {"status": "success", "message": "Webhook server is running"}


@app.post("/")
async def catch_all(request: Request):
headers = request.headers
message_id = headers.get("webhook-id")
timestamp = headers.get("webhook-timestamp")
received_signature = headers.get("webhook-signature")
payload_bytes = await request.body()
payload_json = payload_bytes.decode()
payload = await request.json()

if not all([message_id, timestamp, received_signature, payload]):
raise HTTPException(
status_code=400, detail="Missing required headers or payload"
)

if not verify_signature(message_id, timestamp, payload_json, received_signature):
raise HTTPException(status_code=403, detail="Invalid webhook signature")

return {"status": "success", "message": "Webhook received and verified"}


if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8200)

Validating HTTPS certificates

When using webhooks over HTTPS, you can configure your system to validate certificates to ensure that the receiver has a valid HTTPS certificate, adding an extra layer of security.

Example payloads

Here are some example payloads of some events that may be sent.

{
"data": {
"branch_id": "182c6a84-4af4-736b-35a6-c514e640ca8b",
"branch_name": "test",
"sync_with_git": false
},
"id": "2ece726f-8574-488c-92f7-ee9a56485b46",
"branch": null,
"account_id": "182c6918-8880-5252-3611-c51ae7e0d6a5",
"occured_at": "2025-03-13 16:44:32.321142+00:00",
"event": "infrahub.branch.created"
}

Custom headers

Webhooks can include custom HTTP headers in every request they send. This is useful for authentication tokens, routing metadata, or any other headers the receiving system requires.

Key-value pair types

Custom headers are defined as reusable key-value pair entities that can be shared across multiple webhooks:

  • Static Key-Value (CoreStaticKeyValue): The header value is stored as plain text and sent as-is. Use this for non-sensitive metadata like X-Source-System: infrahub or for authentication tokens like Authorization: Bearer <token>.
  • Environment Variable Key-Value (CoreEnvironmentVariableKeyValue): The stored value is the name of an environment variable. The actual value is resolved from the Prefect worker's environment at the time the webhook request is sent. Use this when secrets are managed externally (for example, Kubernetes secrets or HashiCorp Vault).

Creating and associating headers

  1. Create a key-value pair via the UI or GraphQL API, specifying a name (human-friendly identifier), a key (the HTTP header field name), and a value.
  2. Associate the key-value pair with one or more webhooks using the headers relationship on the webhook.

A single key-value pair can be linked to multiple webhooks (many-to-many relationship). When the key-value pair is updated, all linked webhooks automatically pick up the new value on their next trigger.

Header merge behavior

When a webhook fires, headers are merged in this order:

  1. System defaults: Accept: application/json and Content-Type: application/json
  2. Custom headers: Applied on top of defaults — a custom header with the same key as a system default will override it
  3. HMAC signature headers (if a shared key is configured): webhook-id, webhook-timestamp, and webhook-signature are added last and cannot be overridden by custom headers

Environment variable resolution

For environment-variable-based headers, the value is resolved from os.environ on the Prefect worker at send time:

  • If the environment variable exists, its value is used as the header value.
  • If the environment variable is not found, the header is skipped, a warning is logged, and all remaining headers are still sent.
note

Authentication header values must include the full value including any type prefix. For example, for Bearer token authentication, set the value to Bearer eyJhbGci... (not just the token).

Custom webhook transformation

The data object from the payloads above is passed to the transform method of your Python transform.