Skip to main content
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