Skip to content

Guides

Webhooks

A twin environment can notify your endpoint whenever a call mutatesits state — a customer created, a message posted, an opportunity closed — with a signed event you verify the way you verify Stripe’s. Events are dispatched from the environment’s call ledger, never from sandboxed tool code; reads and failed calls never fire.

Register, verify, replay

API
$ curl -sS -X POST "$BLOBFISH_BASE_URL/api/v1/twin/environments/twe_<id>/webhooks" \
    -H "X-API-Key: $BLOBFISH_API_KEY" -H "Content-Type: application/json" \
    -d '{"url":"https://hooks.example.com/blobfish","events":["stripe.*","slack.chat.postMessage"]}'
# → 201 {"webhook_id":"whk_…","secret":"whsec_…","events":[…],"include_payload":false,"policy":{…}}   (secret shown once)

# every mutating call now delivers:
#   POST <url>   Blobfish-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, t + "." + body)>
#                Blobfish-Event: stripe.PostCustomers   Blobfish-Delivery: whd_…
#   body {"schema_version":"blobfish.twin-webhook-event.v1","id":"evt_…","type":"stripe.PostCustomers","payload_mode":"digest",
#         "data":{"env_id":"twe_…","service":"stripe","operation":"PostCustomers","tool":"stripe_post_customers","protocol":"rest",
#                 "session":"sess_…","args_sha256":"…","change_sha256":"…","ts":"…"}}

$ curl -sS "$BLOBFISH_BASE_URL/api/v1/twin/environments/twe_<id>/webhooks/whk_<id>/deliveries" -H "X-API-Key: $BLOBFISH_API_KEY"
$ curl -sS -X POST "$BLOBFISH_BASE_URL/api/v1/twin/environments/twe_<id>/webhooks/whk_<id>/deliveries/whd_<id>/replay" -H "X-API-Key: $BLOBFISH_API_KEY"
$ curl -sS -X DELETE "$BLOBFISH_BASE_URL/api/v1/twin/environments/twe_<id>/webhooks/whk_<id>" -H "X-API-Key: $BLOBFISH_API_KEY"
$ blobfish twin-runs webhooks add twe_<id> --url https://hooks.example.com/blobfish     # CLI shape
Verify (python)
import hmac, hashlib
def verify(secret: str, signature_header: str, body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Policy on this deployment

RuleValue
Payloaddigest by default — event name, operation, tool, session, request id, and sha256 of the arguments and of the change set; include_payload: true adds the change set, capped at 32 KB.
Endpointhttps, publicly resolvable host (the same guard as custom model endpoints); never loopback or private ranges.
Limits10 registrations per environment; 500 deliveries per environment per hour; a bounded in-process queue. Past a limit the registration is refused (webhook_limit) or the delivery is counted, never silently dropped.
Retries3 attempts (1 s, 5 s, 25 s apart), 5 s per request; then dead. The last 100 deliveries stay readable and replayable.
SignatureBlobfish-Signature: t=<unix>,v1=<HMAC-SHA256(secret, t.body)>; a five-minute tolerance is the verifier’s default. Secrets derive from a server-side pepper and the shown-once value; the server keeps a hash.

Vendor-shaped signatures (Stripe-Signature, GitHub’s X-Hub-Signature-256) are not emulated yet: a twin webhook is a Blobfish event about a vendor operation, not the vendor’s own webhook. Cluster-plane (tenv_) environments do not serve webhooks or environment tokens.

Next

Gateway & health · Local testing · Quickstart