NullSpend Docs

Webhook Event Types

NullSpend emits 15 event types. Each event is delivered as an HTTP POST with a JSON body.

NullSpend emits 15 event types. Each event is delivered as an HTTP POST with a JSON body.

Event Envelope

Full Event

{
  "id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "cost_event.created",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": { }
  }
}

Thin Event

Used for cost_event.created on endpoints with payloadMode: "thin". All other event types always use the full envelope.

{
  "id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "cost_event.created",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "related_object": {
    "id": "req_xyz",
    "type": "cost_event",
    "url": "/api/cost-events?requestId=req_xyz&provider=openai"
  }
}
FieldTypeDescription
idstringUnique event ID (evt_ + UUID). Use for deduplication.
typestringOne of the 15 event types below.
api_versionstringAPI version ("2026-04-01").
created_atintegerUnix timestamp in seconds.
data.objectobjectEvent-specific payload (full mode).
related_objectobjectReference to fetchable object (thin mode).

Cost Events

cost_event.created

Fires when a cost event is recorded — once per proxied request.

data.object fields:

FieldTypeDescription
request_idstringUnique request identifier
event_typestringRequest type: "llm" (LLM API call), "tool" (MCP tool invocation), or "custom" (SDK-reported)
providerstring"openai" or "anthropic"
modelstringModel name (e.g., gpt-4o)
input_tokensintegerTotal input tokens
output_tokensintegerOutput tokens
cached_input_tokensintegerCached input tokens
cost_microdollarsintegerTotal cost in microdollars
duration_msintegerRequest duration in milliseconds
upstream_duration_msinteger or nullTime spent waiting for the LLM provider
session_idstring or nullSession ID if set
trace_idstring or nullTrace ID
tool_namestring or nullMCP tool name
tool_serverstring or nullMCP tool server
tool_calls_requestedarray or nullArray of {name, id} objects representing tool calls, or null
tool_definition_tokensintegerToken count for tool definitions (defaults to 0)
api_key_idstringAPI key that made the request
sourcestring"proxy", "api", or "mcp"
tagsobjectKey-value pairs from X-NullSpend-Tags
created_atstringISO 8601 timestamp

Example:

{
  "id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "cost_event.created",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "request_id": "chatcmpl-abc123",
      "event_type": "llm",
      "provider": "openai",
      "model": "gpt-4o",
      "input_tokens": 1000,
      "output_tokens": 500,
      "cached_input_tokens": 200,
      "cost_microdollars": 7,
      "duration_ms": 1234,
      "upstream_duration_ms": 1180,
      "session_id": null,
      "trace_id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
      "tool_name": null,
      "tool_server": null,
      "tool_calls_requested": null,
      "tool_definition_tokens": 0,
      "api_key_id": "ns_key_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "source": "proxy",
      "tags": { "team": "billing", "env": "production" },
      "created_at": "2026-03-21T12:00:00.000Z"
    }
  }
}

Budget Events

budget.threshold.warning

Fires when spend crosses a threshold percentage below 90% (e.g., 50%, 80%).

data.object fields:

FieldTypeDescription
budget_entity_typestring"user", "api_key", or "tag"
budget_entity_idstringEntity identifier
threshold_percentintegerThreshold crossed (e.g., 80)
budget_spend_microdollarsintegerCurrent spend
budget_limit_microdollarsintegerBudget ceiling
budget_remaining_microdollarsintegerRemaining budget (limit minus spend)
triggered_by_request_idstringRequest that triggered the crossing

Example:

{
  "id": "evt_b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "type": "budget.threshold.warning",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "budget_entity_type": "api_key",
      "budget_entity_id": "ns_key_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "threshold_percent": 80,
      "budget_spend_microdollars": 40500000,
      "budget_limit_microdollars": 50000000,
      "budget_remaining_microdollars": 9500000,
      "triggered_by_request_id": "chatcmpl-abc123"
    }
  }
}

budget.threshold.critical

Same structure as budget.threshold.warning. Fires when spend crosses a threshold ≥ 90%.

{
  "id": "evt_c3d4e5f6-a7b8-9012-cdef-123456789012",
  "type": "budget.threshold.critical",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "budget_entity_type": "user",
      "budget_entity_id": "user_12345",
      "threshold_percent": 95,
      "budget_spend_microdollars": 47800000,
      "budget_limit_microdollars": 50000000,
      "budget_remaining_microdollars": 2200000,
      "triggered_by_request_id": "chatcmpl-def456"
    }
  }
}

budget.exceeded

Fires when a request is blocked because the budget ceiling was hit.

data.object fields:

FieldTypeDescription
budget_entity_typestringEntity type
budget_entity_idstringEntity identifier
budget_limit_microdollarsintegerBudget ceiling
budget_spend_microdollarsintegerCurrent spend
estimated_request_cost_microdollarsintegerEstimated cost of the blocked request
modelstringRequested model
providerstringProvider name
blocked_atstringISO 8601 timestamp when blocked

Example:

{
  "id": "evt_d4e5f6a7-b8c9-0123-defa-234567890123",
  "type": "budget.exceeded",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "budget_entity_type": "api_key",
      "budget_entity_id": "ns_key_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "budget_limit_microdollars": 50000000,
      "budget_spend_microdollars": 49800000,
      "estimated_request_cost_microdollars": 500000,
      "model": "gpt-4o",
      "provider": "openai",
      "blocked_at": "2026-03-21T12:00:00.000Z"
    }
  }
}

budget.reset

Fires when a budget period resets (daily, weekly, or monthly).

data.object fields:

FieldTypeDescription
budget_entity_typestringEntity type
budget_entity_idstringEntity identifier
budget_limit_microdollarsintegerBudget ceiling
previous_spend_microdollarsintegerSpend in the period that just ended
new_period_startstringISO 8601 timestamp of the new period
reset_intervalstring"daily", "weekly", or "monthly"

Example:

{
  "id": "evt_e5f6a7b8-c9d0-1234-efab-345678901234",
  "type": "budget.reset",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "budget_entity_type": "user",
      "budget_entity_id": "user_12345",
      "budget_limit_microdollars": 50000000,
      "previous_spend_microdollars": 42000000,
      "new_period_start": "2026-04-01T00:00:00.000Z",
      "reset_interval": "monthly"
    }
  }
}

Enforcement Events

request.blocked

Fires when a request is blocked for any reason.

data.object fields:

FieldTypeDescription
reasonstring"budget", "rate_limit", or "policy"
modelstringRequested model
providerstringProvider name
api_key_idstringAPI key
detailsobject or nullAdditional context (varies by reason)

Example:

{
  "id": "evt_f6a7b8c9-d0e1-2345-fabc-456789012345",
  "type": "request.blocked",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "reason": "budget",
      "model": "gpt-4o",
      "provider": "openai",
      "api_key_id": "ns_key_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "details": null
    }
  }
}

velocity.exceeded

Fires when a velocity limit trips the circuit breaker.

data.object fields:

FieldTypeDescription
budget_entity_typestringEntity type
budget_entity_idstringEntity identifier
velocity_limit_microdollarsintegerConfigured velocity limit
velocity_window_secondsintegerSliding window size
velocity_current_microdollarsintegerSpend in the current window
cooldown_secondsintegerHow long requests will be blocked
modelstringRequested model
providerstringProvider name
blocked_atstringISO 8601 timestamp when blocked

Example:

{
  "id": "evt_a7b8c9d0-e1f2-3456-abcd-567890123456",
  "type": "velocity.exceeded",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "budget_entity_type": "api_key",
      "budget_entity_id": "ns_key_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "velocity_limit_microdollars": 10000000,
      "velocity_window_seconds": 60,
      "velocity_current_microdollars": 10500000,
      "cooldown_seconds": 60,
      "model": "gpt-4o",
      "provider": "openai",
      "blocked_at": "2026-03-21T12:00:00.000Z"
    }
  }
}

velocity.recovered

Fires when the velocity circuit breaker closes after cooldown.

data.object fields:

FieldTypeDescription
budget_entity_typestringEntity type
budget_entity_idstringEntity identifier
velocity_limit_microdollarsintegerConfigured velocity limit
velocity_window_secondsintegerSliding window size
velocity_cooldown_secondsintegerCooldown duration that just ended
recovered_atstringISO 8601 timestamp when recovered

Example:

{
  "id": "evt_b8c9d0e1-f2a3-4567-bcde-678901234567",
  "type": "velocity.recovered",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "budget_entity_type": "api_key",
      "budget_entity_id": "ns_key_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "velocity_limit_microdollars": 10000000,
      "velocity_window_seconds": 60,
      "velocity_cooldown_seconds": 60,
      "recovered_at": "2026-03-21T12:01:00.000Z"
    }
  }
}

session.limit_exceeded

Fires when a session's cumulative spend exceeds the session limit.

data.object fields:

FieldTypeDescription
budget_entity_typestringEntity type
budget_entity_idstringEntity identifier
session_idstringThe session that was capped
session_spend_microdollarsintegerCumulative session spend
session_limit_microdollarsintegerConfigured session limit
modelstringRequested model
providerstringProvider name
blocked_atstringISO 8601 timestamp when blocked

Example:

{
  "id": "evt_c9d0e1f2-a3b4-5678-cdef-789012345678",
  "type": "session.limit_exceeded",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "budget_entity_type": "user",
      "budget_entity_id": "user_12345",
      "session_id": "conv_abc123",
      "session_spend_microdollars": 4800000,
      "session_limit_microdollars": 5000000,
      "model": "gpt-4o",
      "provider": "openai",
      "blocked_at": "2026-03-21T12:00:00.000Z"
    }
  }
}

tag_budget.exceeded

Fires when a tag-level budget is exceeded.

data.object fields:

FieldTypeDescription
budget_entity_typestring"tag"
budget_entity_idstringTag entity ID (key=value)
tag_keystringTag key
tag_valuestringTag value
budget_limit_microdollarsintegerTag budget ceiling
budget_spend_microdollarsintegerCurrent tag spend
estimated_request_cost_microdollarsintegerEstimated cost of the blocked request
modelstringRequested model
providerstringProvider name
blocked_atstringISO 8601 timestamp when blocked

Example:

{
  "id": "evt_d0e1f2a3-b4c5-6789-defa-890123456789",
  "type": "tag_budget.exceeded",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "budget_entity_type": "tag",
      "budget_entity_id": "team=billing",
      "tag_key": "team",
      "tag_value": "billing",
      "budget_limit_microdollars": 50000000,
      "budget_spend_microdollars": 49500000,
      "estimated_request_cost_microdollars": 500000,
      "model": "gpt-4o",
      "provider": "openai",
      "blocked_at": "2026-03-21T12:00:00.000Z"
    }
  }
}

HITL Action Events

See Human-in-the-Loop for the full approval workflow, state machine, and SDK integration.

action.created

Fires when a human-in-the-loop approval action is created.

data.object fields:

FieldTypeDescription
action_idstringAction identifier (ns_act_ + UUID)
action_typestringType of action
agent_idstringAgent that created the action
statusstring"pending"
payloadobjectAction payload (the data submitted for approval)
created_atstringISO 8601 timestamp
expires_atstring or nullWhen the action expires if not acted on

Example:

{
  "id": "evt_e1f2a3b4-c5d6-7890-efab-901234567890",
  "type": "action.created",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "action_id": "ns_act_550e8400-e29b-41d4-a716-446655440000",
      "action_type": "http_post",
      "agent_id": "my-agent",
      "status": "pending",
      "payload": { "amount": 500, "description": "Large purchase" },
      "created_at": "2026-03-21T12:00:00.000Z",
      "expires_at": "2026-03-21T13:00:00.000Z"
    }
  }
}

action.approved

Fires when an action is approved.

data.object fields:

FieldTypeDescription
action_idstringAction identifier
action_typestringType of action
agent_idstringAgent that created the action
statusstring"approved"
approved_bystring or nullUser who approved
approved_atstring or nullISO 8601 timestamp of approval

action.rejected

Fires when an action is rejected.

data.object fields:

FieldTypeDescription
action_idstringAction identifier
action_typestringType of action
agent_idstringAgent that created the action
statusstring"rejected"
rejected_bystring or nullUser who rejected
rejected_atstring or nullISO 8601 timestamp of rejection
reasonstring or nullRejection reason

action.expired

Fires when an action's TTL expires.

data.object fields:

FieldTypeDescription
action_idstringAction identifier
action_typestringType of action
agent_idstringAgent that created the action
statusstring"expired"
expired_atstring or nullISO 8601 timestamp of expiry

Test Events

test.ping

Sent when you click "Test" in the dashboard. Use it to verify your endpoint is reachable and signature verification works.

Example:

{
  "id": "evt_f2a3b4c5-d6e7-8901-fabc-012345678901",
  "type": "test.ping",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "message": "Test webhook event"
    }
  }
}

On this page