ThrustLab

Webhooks

ThrustLab webhooks deliver server-side events (simulation lifecycle, sweep lifecycle, compute-unit balance, component review) to your HTTPS endpoint as signed POST requests. Delivery is at-least-once, with HMAC-SHA256 signatures, deterministic backoff retries, and a 7-day auto-disable safety net.

The full surface lives under /v1/webhook_endpoints. See the OpenAPI reference for request/response schemas. This page covers the developer-facing semantics: registration, signature verification, the event catalog, retry behavior, auto-disable, the test event, the delivery debug surface, and source IP allowlisting.

Registration

Register an endpoint via POST /v1/webhook_endpoints:

curl -X POST https://thrustlab.com/v1/webhook_endpoints \
-H "Authorization: Bearer key_..." \
-H "Content-Type: application/json" \
-d '{
  "url": "https://yourapp.example.com/hooks/thrustlab",
  "events": ["simulation.completed", "simulation.failed"],
  "description": "Prod ingest"
}'

The response includes the signing secret (whsec_<43-char-base64url>, total 49 chars) exactly once. Store it immediately — every subsequent read of this endpoint returns secret: null plus a four-character secret_hint. Lose it and you must call POST /v1/webhook_endpoints/{id}/rotate_secret for a new one (which invalidates the old one immediately).

To subscribe to every event type, pass ["*"]. Otherwise pass an exact list; unknown event types are rejected at create-time.

URL constraints: HTTPS only, no private/loopback/link-local IPs (SSRF guard), DNS must resolve.

Signature verification

Every delivery includes a Thrustlab-Signature header in the form t=<unix-ts>,v1=<hex-sha256>. The signed payload is f"{t}.{raw_body}" — concatenate the timestamp, a literal dot, and the raw request body bytes received off the wire.

Critical: verify against the raw body. Re-serializing the JSON before computing the HMAC (e.g. json.dumps(json.loads(body))) is the #1 webhook signature bug — sort order, whitespace, and Unicode escaping will not match what we signed.

Complete handler example (Python, FastAPI):

"""Example FastAPI webhook handler verifying incoming events."""
import os
from fastapi import FastAPI, Request, HTTPException
from thrustlab import Webhook
from thrustlab.exceptions import SignatureVerificationError

app = FastAPI()
WEBHOOK_SECRET = os.environ["THRUSTLAB_WEBHOOK_SECRET"]


@app.post("/webhooks/thrustlab")
async def thrustlab_webhook(request: Request):
    payload = await request.body()
    sig = request.headers.get("Thrustlab-Signature", "")
    try:
        event = Webhook.verify(payload, sig, WEBHOOK_SECRET)
    except SignatureVerificationError as exc:
        raise HTTPException(status_code=400, detail=str(exc))

    if event.type == "simulation.succeeded":
        sim_id = event.data["id"]
        # ... do work ...

    return {"received": True}

The SDK's Webhook.verify() helper handles:

  1. Timestamp freshness — rejects events older than 5 minutes. Replay protection.
  2. Constant-time comparison — uses hmac.compare_digest internally. Immune to timing attacks.
  3. Raw body verification — operates on the request body bytes received off the wire, not a decoded-then-reserialized copy.

Replay protection

Each delivery additionally carries a Webhook-Id: evt_<ksuid> header and a Webhook-Attempt header (1-based attempt number — 1 on the first try, 2 on the first retry, and so on). Server-side delivery is at-least-once — a 2xx response lost mid-network results in the same evt_xxx arriving twice. Deduplicate on Webhook-Id for exactly-once processing.

Combined with the 5-minute timestamp tolerance above, this gives you full replay defense.

Event catalog

Event typeFired when
simulation.queuedA new simulation is accepted and queued.
simulation.runningA queued simulation has started executing on a worker.
simulation.completedA simulation finished successfully.
simulation.failedA simulation terminated with an error.
simulation.canceledA simulation was canceled by the user before completion.
sweep.queuedA new sweep is accepted and its child simulations are being queued.
sweep.runningA sweep has at least one child simulation running.
sweep.completedAll child simulations of a sweep finished successfully.
sweep.failedA sweep terminated with at least one failed child.
sweep.canceledA sweep was canceled by the user.
credits.low_balanceThe authenticated user's compute-unit balance crossed below the low-balance threshold.
component.submittedA user-submitted component awaits moderator review.
component.approvedA submitted component was approved and is now visible.
component.rejectedA submitted component was rejected by a moderator.
webhook.testSynthetic test event fired by POST /v1/webhook_endpoints/{id}/test; never fired by the regular fan-out path.

To subscribe to every present and future type, register with events: ["*"]. Otherwise the registration is exact-match — adding a new event type later requires a PATCH to the endpoint's events array.

credits.low_balance payload

The data.object for credits.low_balance is the full CreditBalance resource — the same shape returned by GET /v1/compute-units/balance. Webhook consumers can route off total or render the breakdown directly.

{
  "id": "evt_2c5tQ...",
  "object": "event",
  "type": "credits.low_balance",
  "created_at": "2026-04-25T15:42:11.123Z",
  "api_version": "beta",
  "data": {
    "object": {
      "object": "credit_balance",
      "total": 95,
      "breakdown": [
        {"type": "free",    "balance": 30, "expires_at": null},
        {"type": "monthly", "balance": 65, "expires_at": "2026-05-25T00:00:00.000Z"}
      ],
      "currency": "credit",
      "low_balance_threshold": 200,
      "as_of": "2026-04-25T15:42:11.123Z"
    }
  }
}

The event fires once per crossing (above-threshold → below-threshold). It does not re-fire for additional consumptions while already below the threshold. If a refund or grant returns the balance to at-or-above the threshold, the detector rearms and will fire again on the next crossing. Free-tier and beta users (no recurring monthly grant) do not receive this event — the threshold is null for them.

See compute units for the full balance and usage-history documentation.

Retry and backoff schedule

Each delivery gets up to 5 attempts. Backoff between attempts:

AttemptDelay since previous failure
1immediate
2+1 minute
3+30 minutes
4+2 hours
5+24 hours

Total wall-clock window from first attempt to permanent failure: ~26.5 hours (~27h, rounded). After attempt 5 fails the delivery transitions to permanently_failed and the consecutive-failure counter on the endpoint advances by one.

Retry triggers: HTTP 5xx, HTTP 429, and network/timeout errors (10-second connect+read timeout per attempt).

Retry skipped (terminal immediately): HTTP 404 and HTTP 410. These are treated as "this URL is gone" and burn the entire attempt budget on the spot.

Any 2xx response — body content irrelevant — marks the delivery succeeded and resets the endpoint's consecutive-failure counter.

Auto-disable

If an endpoint has no successful delivery for 7 days while at least one delivery has failed in that window, the server flips enabled=false and stamps auto_disabled_at. Pending deliveries on a disabled endpoint are skipped (not retried).

To re-enable, PATCH /v1/webhook_endpoints/{id} with {"enabled": true}. The auto_disabled_at timestamp is cleared and the consecutive-failure counter resets.

You may also receive an endpoint disabled notification email when auto-disable fires (the email task is gated on broader email infrastructure; the webhook subsystem itself disables cleanly regardless).

Test endpoint

curl -X POST https://thrustlab.com/v1/webhook_endpoints/wh_.../test \
-H "Authorization: Bearer key_..."

Returns a webhook_delivery row (HTTP 202). The server fires a synthetic webhook.test event scoped to that one endpoint — bypassing the regular fan-out, so other endpoints subscribed to * will not receive it. Useful for validating signature verification, allowlist rules, and TLS terminators end-to-end.

The data.object payload is a small static body containing endpoint_id, a friendly message, and the trigger timestamp.

Delivery debug surface

Every dispatch and retry creates a row in webhook_deliveries, viewable via:

  • GET /v1/webhook_endpoints/{id}/deliveries — cursor-paginated list of deliveries for that endpoint, newest first. Filter by status (pending / succeeded / permanently_failed) and event type.
  • GET /v1/webhook_endpoints/{id}/deliveries/{whd_id} — full detail: request_body (the canonical JSON we signed), response_status_code, response_body_excerpt (first 1 KB of your response), error_message, attempt_count, last_attempt_at, next_attempt_at, and manual_retry_of (the prior whd_ if this row is a manual replay).

To replay a terminal delivery (succeeded or permanently_failed), use:

curl -X POST https://thrustlab.com/v1/webhook_endpoints/wh_.../deliveries/whd_.../retry \
  -H "Authorization: Bearer key_..."

This creates a fresh delivery row with manual_retry_of pointing back at the source. The worker re-signs the original canonical body (preserved on the source row), so retries still verify even if the source event has since been pruned by the 30-day retention sweep. Pending deliveries cannot be retried (HTTP 409); wait for them to terminate.

Source IP allowlisting

All webhook deliveries originate from the ThrustLab production VPS. If your firewall does egress filtering, allow inbound HTTPS from:

  • 5.161.233.170

This IP is stable for the foreseeable future. We will publish a deprecation notice well in advance of any change.

Forward-looking subscriptions

Webhook subscriptions are forward-looking. An endpoint only receives events created at or after the endpoint's own created_at timestamp. We do not retro-deliver historical events to newly-registered endpoints.

If you need historical events — e.g. you're rebuilding a downstream cache, or you missed a window of deliveries — pull from the events resource directly. GET /v1/events returns the same event objects that webhooks deliver, filterable by type, resource_id, and created_at range, with 30-day retention.

We use cookies and analytics tools to understand how you use ThrustLab and improve the experience. Privacy Policy

Webhooks · ThrustLab API