SOLWYN
Webhooks

Events

Reference for every webhook event type Solwyn emits, with per-type payload shape and an example payload.

Solwyn webhooks deliver budget and circuit-breaker event types today, with two provider-health types reserved in the contract for future emitters. Use a webhook's event filter (* or a comma-separated list of event types) to control which events your endpoint receives.

Currently emitted

These event types are delivered today.

budget_threshold

Fired when a project's spend crosses one of its configured budget thresholds below 100% (for example, 50% or 80% of the period budget). Each threshold is fired at most once per period — once the threshold has been crossed for the period, it will not refire even if usage dips and recrosses.

Payload data fields:

FieldTypeDescription
project_namestringHuman-readable project name.
budget_limitnumberPeriod budget in USD.
current_usagenumberPeriod spend in USD at time of fire.
threshold_pctintegerThe threshold that was crossed (e.g. 50, 80).
periodstringOne of daily, weekly, monthly.
period_datestringPeriod bucket: YYYY-MM-DD for daily, YYYY-Www (ISO week) for weekly, YYYY-MM for monthly.
channelsarrayNotification channels configured for this threshold (e.g. ["webhook", "email"]).

Example:

{
  "event_type": "budget_threshold",
  "project_id": "proj_a1b2c3d4e5f6a7b8c9d0e1f2",
  "timestamp": "2026-05-05T18:00:00.123456+00:00",
  "idempotency_key": "notification:7f3a9d2c815e4b6f0a1d8c3e5b7f9a2d4c6e8b0f1a3d5c7e9b2f4a6c8e0d1b3f",
  "data": {
    "project_name": "production",
    "budget_limit": 100.0,
    "current_usage": 80.5,
    "threshold_pct": 80,
    "period": "monthly",
    "period_date": "2026-05",
    "channels": ["webhook", "email"]
  }
}

budget_exceeded

Fired when a project's spend crosses 100% of its period budget. Same payload shape as budget_threshold, with threshold_pct set to the configured 100% threshold. This is a separate event type so you can route it to a more urgent channel than the lower-percentage warnings.

Example:

{
  "event_type": "budget_exceeded",
  "project_id": "proj_a1b2c3d4e5f6a7b8c9d0e1f2",
  "timestamp": "2026-05-05T19:30:00.654321+00:00",
  "idempotency_key": "notification:2b8e6d4f0c9a7315e8b2d6f4a0c8e1b3d7f5a9c2e6b0d8f4a2c6e0b4d8f2a6c1",
  "data": {
    "project_name": "production",
    "budget_limit": 100.0,
    "current_usage": 102.4,
    "threshold_pct": 100,
    "period": "monthly",
    "period_date": "2026-05",
    "channels": ["webhook", "email", "pagerduty"]
  }
}

circuit_breaker_opened

Fired when an SDK instance reports its per-provider circuit breaker transitioning into the open state — including the first snapshot Solwyn Cloud receives from an instance when that snapshot already reports an open breaker. Severity is critical. Transitions into half-open are silent, as are replayed or unchanged snapshots.

The producer is the SDK's breaker state reporting (SDK v0.3.0+, on by default via breaker_reporting_enabled). Events are scoped to a single project, provider, and SDK instance: every instance reports its own in-process breaker, so several instances calling the same provider each fire their own events — one instance's open breaker does not mean the provider is down for everyone. Breaker events deliver over webhooks and the notification channels; they do not appear in the dashboard's in-app alerts feed, which stays budget-only.

Payload data fields:

FieldTypeDescription
providerstringProvider the breaker tracks (e.g. openai).
provider_namestringSame value as provider — both are sent.
statestringReported breaker state; open for this event type.
failure_countintegerConsecutive failures recorded against the breaker.
success_countintegerConsecutive successes recorded against the breaker.
reasonstringWhy the breaker opened. Opened events only.
reported_atstringISO 8601 UTC time the SDK took the snapshot — distinct from the envelope's dispatch timestamp.
sdk_instance_idstringThe reporting client instance's id (a fresh UUID per SDK client instance).

Example:

{
  "event_type": "circuit_breaker_opened",
  "project_id": "proj_a1b2c3d4e5f6a7b8c9d0e1f2",
  "timestamp": "2026-05-05T20:14:03.221417+00:00",
  "idempotency_key": "breaker-transition:e3d1c5a7f9b2d4e6a8c0f1b3d5e7a9c2f4b6d8e0a1c3f5b7d9e2a4c6f8b0d1e3",
  "data": {
    "provider": "openai",
    "provider_name": "openai",
    "state": "open",
    "failure_count": 5,
    "success_count": 0,
    "reason": "Circuit breaker reported open after 5 failures",
    "reported_at": "2026-05-05T20:14:01.008932+00:00",
    "sdk_instance_id": "8f14e45f-ceea-467a-9f2c-3b1d6a7e40c5"
  }
}

circuit_breaker_closed

Fired when a reported breaker returns to closed from either open or half-open — the recovery counterpart to circuit_breaker_opened. Severity is info. A first-ever snapshot that arrives already closed is silent, as are replayed or unchanged snapshots. Scope and producer are the same as the opened event: one project, one provider, one SDK instance.

Each project's delivery defaults carry a severity threshold (warning by default). This event is info-severity but is deliberately exempt from the severity floor — resolution events always pass the severity filter, so an incident that opened can always be closed out. The per-target event filter still applies: turn the event type off and you receive nothing.

Payload data fields: the same shape as circuit_breaker_opened, without reason, plus:

FieldTypeDescription
downtime_minutesintegerWhole minutes the breaker was open. Closed events only.

Example:

{
  "event_type": "circuit_breaker_closed",
  "project_id": "proj_a1b2c3d4e5f6a7b8c9d0e1f2",
  "timestamp": "2026-05-05T20:31:47.905118+00:00",
  "idempotency_key": "breaker-transition:4a7c1e9b3d5f2a8c6e0b4d7f1a3c9e5b8d2f6a0c4e7b1d9f3a5c8e2b6d0f4a7c",
  "data": {
    "provider": "openai",
    "provider_name": "openai",
    "state": "closed",
    "failure_count": 0,
    "success_count": 2,
    "downtime_minutes": 17,
    "reported_at": "2026-05-05T20:31:45.412006+00:00",
    "sdk_instance_id": "8f14e45f-ceea-467a-9f2c-3b1d6a7e40c5"
  }
}

Reserved

provider_outage and provider_recovered are part of the contract — adapters are wired up to receive them and webhook filters accept them as valid values — but nothing emits them yet. Circuit-breaker events carry the per-instance view of provider health, and a provider-wide outage is a different claim: one SDK instance's breaker opening does not establish that a provider is down for your account. These two types stay reserved until an emitter can make that account-wide determination.

If you build handlers for these now, expect to revise the payload shape once the emitters ship; the field names below reflect the consumer-side expectation, not a frozen contract.

provider_outage

Will fire when an upstream LLM provider (OpenAI, Anthropic, Google) is detected as experiencing an outage that affects projects on your account.

Expected data fields: provider_name, detected_at, affected_projects.

provider_recovered

Will fire when a previously degraded provider returns to healthy operation.

Expected data fields: provider_name, downtime_minutes.

Idempotency

Every delivery carries an Idempotency-Key HTTP request header and a matching idempotency_key field in the JSON payload. The key identifies the event itself and is stable across retries and redeliveries, so dedupe on it directly — a "have I seen this key before" check is all you need. For circuit-breaker transitions the key is a stable hash of the project, provider, SDK instance, state, and snapshot time, so the same transition always resolves to the same key.

The signature covers the full payload including idempotency_key, so verification against the raw bytes needs no change — see Verifying signatures.

The natural key for each event type is still useful for correlating events with your own records:

  • Budget events: (event_type, project_id, period, period_date, threshold_pct) uniquely identifies a single threshold crossing for a project's budget period. The same threshold will not refire within the same period bucket.
  • Circuit-breaker events: (event_type, project_id, provider, sdk_instance_id, reported_at) identifies one instance's breaker transition. Correlate an opened event with its closed event on project_id, provider, and sdk_instance_id.

Solwyn does not retry events past the 3-attempt delivery limit, so duplicate deliveries are limited to those 3 attempts. A simple "have I seen this key in the last hour" check is sufficient.

On this page