Skip to main content

Documentation Index

Fetch the complete documentation index at: https://hc.starbridge.ai/llms.txt

Use this file to discover all available pages before exploring further.

Starbridge signs every webhook using Ed25519 asymmetric signatures, following the Standard Webhooks specification.

How it works

  1. Starbridge constructs a message by concatenating the webhook ID, timestamp, and body with dots:
    {webhook-id}.{webhook-timestamp}.{body}
    
  2. This message is signed with Starbridge’s private key using Ed25519.
  3. The signature is base64-encoded and sent in the webhook-signature header with a v1a, prefix.

Verification steps

To verify a webhook:
  1. Extract the headers — read webhook-id, webhook-timestamp, and webhook-signature from the request.
  2. Read the raw body — use the raw request body. Do not parse and re-serialize the JSON, as even minor formatting changes will break verification.
  3. Reconstruct the signed message:
    {webhook-id}.{webhook-timestamp}.{raw_body}
    
  4. Strip the v1a, prefix from the webhook-signature header and base64-decode the remainder to get the signature bytes.
  5. Strip the whpk_ prefix from your public key and base64-decode the remainder to get the key bytes.
  6. Verify the Ed25519 signature using your language’s crypto library.

Timestamp validation

To protect against replay attacks, check that the webhook-timestamp is recent (within 5 minutes of the current time). Reject requests with timestamps that are too old.

Idempotency

Use the webhook-id header as an idempotency key. Store recently processed webhook IDs and skip any duplicates to avoid processing the same event twice.

Code examples

All examples use the following test data:
ParameterValue
Public keywhpk_MCowBQYDK2VwAyEA3pXFbrxQWyCihnqd8eQ7B0CVyB9BXRMes8oluz6YkA8=
webhook-idmsg_350d6ad7-13e1-47f8-8111-33f96313fabe
webhook-timestamp1775589603
webhook-signaturev1a,oB5S13GytYJTAULdngi9wtz1YHs9hNppi75xw3W8VR69ypeI/hCwwlTWePup7J7ZXr4wdKPDdlWxS3o1YuxkAg==
import base64
from cryptography.hazmat.primitives.serialization import load_der_public_key

def verify_webhook(public_key_str, webhook_id, webhook_timestamp, webhook_signature, body):
    """
    Verify a Starbridge webhook signature.

    Args:
        public_key_str: Your public key (e.g. "whpk_MCow...")
        webhook_id: Value of the webhook-id header
        webhook_timestamp: Value of the webhook-timestamp header
        webhook_signature: Value of the webhook-signature header
        body: Raw request body as bytes
    Returns:
        True if the signature is valid
    Raises:
        Exception if verification fails
    """
    # 1. Strip the whpk_ prefix and decode the public key
    raw_key = public_key_str.removeprefix("whpk_")
    key_bytes = base64.b64decode(raw_key)
    public_key = load_der_public_key(key_bytes)

    # 2. Strip the v1a, prefix and decode the signature
    if not webhook_signature.startswith("v1a,"):
        raise ValueError("Unsupported signature version")
    sig_bytes = base64.b64decode(webhook_signature[4:])

    # 3. Reconstruct the signed message
    message = f"{webhook_id}.{webhook_timestamp}.".encode() + body

    # 4. Verify
    public_key.verify(sig_bytes, message)  # Raises InvalidSignature if invalid
    return True
Flask example:
from flask import Flask, request

STARBRIDGE_PUBLIC_KEY = "whpk_MCowBQYDK2VwAyEA3pXFbrxQWyCihnqd8eQ7B0CVyB9BXRMes8oluz6YkA8="

app = Flask(__name__)

@app.route("/webhook", methods=["POST"])
def handle_webhook():
    try:
        verify_webhook(
            STARBRIDGE_PUBLIC_KEY,
            request.headers["webhook-id"],
            request.headers["webhook-timestamp"],
            request.headers["webhook-signature"],
            request.get_data(),  # Raw body bytes
        )
    except Exception:
        return "Invalid signature", 401

    payload = request.get_json()
    # Process the webhook...
    return "OK", 200
Install the dependency:
pip install cryptography