Skip to main content

Events

Events are the foundation of usage-based billing in Monk. When customers use your product, you send events to Monk, which aggregates them into billable usage on invoices.

What is an Event?

An event is a record of something billable happening in your system:
{
  "customerId": "cust-uuid",
  "eventName": "api_call",
  "properties": {
    "endpoint": "/api/users",
    "method": "GET"
  },
  "timestamp": "2026-02-13T10:30:00Z"
}
Events are:
  • Immutable — Once accepted, events cannot be modified
  • Timestamped — Each event has a timestamp for billing period assignment
  • Customer-attributed — Every event is tied to a customer
  • Schema-flexible — Properties can contain any key-value metadata

How Events Work

  1. Ingestion — Your app sends events via the Events API
  2. Storage — Events are durably persisted
  3. Aggregation — Meters query events and compute usage (COUNT, SUM, MAX, etc.)
  4. Billing — Aggregated usage updates invoice line items

Event Schema

FieldRequiredDescription
customerIdYes*Monk’s internal customer UUID
externalCustomerIdYes*Your external customer identifier
eventNameYesIdentifies the type of event
propertiesNoKey-value metadata (strings, numbers, booleans)
timestampNoISO 8601 timestamp (defaults to now). Must not be more than 1 hour in the future.
idempotencyKeyNoUnique key to prevent duplicates
*Either customerId or externalCustomerId must be provided.

Properties

Properties are flexible metadata attached to each event:
{
  "eventName": "api_call",
  "properties": {
    "endpoint": "/v1/chat",
    "model": "gpt-4",
    "input_tokens": 150,
    "output_tokens": 500,
    "region": "us-east-1"
  }
}
Use properties to:
  • Aggregate values — Sum a property like tokens for token-based billing
  • Filter events — Bill differently by region or model
  • Audit and debug — Track context for each billable action
Property values must be primitives (string, number, boolean). Nested objects and arrays are not supported.

Timestamp Validation

The timestamp field determines which billing period an event falls into. Monk enforces the following rules:
  • If omitted, defaults to the current time (UTC)
  • Must be a valid ISO 8601 string (e.g. 2026-03-06T14:30:00Z)
  • Must not be more than 1 hour in the future — events with timestamps further ahead are rejected with a validation error
This prevents accidental mis-attribution of usage to future billing periods due to clock skew or client bugs.

Idempotency

Idempotency keys prevent duplicate events from affecting billing.

How It Works

  1. You send an event with "idempotencyKey": "evt_123"
  2. Monk accepts the event and stores it
  3. If you retry with the same key, Monk accepts the request again (no error) — the latest version of the event becomes the source of truth
  4. Only one copy of the event is counted toward billing
Monk always returns a success response for valid events — retries are safe and won’t produce errors. If you send the same idempotency key with updated data, the most recent version is used for billing.

Best Practices

// Include context that makes the key unique to this specific event
const idempotencyKey = `${eventName}_${customerId}_${requestId}`;
Good idempotency keys include:
  • Event type: api_call_...
  • Customer: ..._cust123_...
  • Unique identifier: ..._req456 or timestamp
If you omit idempotencyKey, Monk auto-generates a UUID. This is fine for fire-and-forget scenarios but doesn’t protect against duplicates on retry

Event Matching

For an event to affect billing, the following must be true:
  1. Customer must exist in Monk
  2. Customer must have an active contract
  3. The contract must include a meter that matches the event (via event type filters and/or property filters)
  4. The event timestamp must fall within an active billing period
Events that don’t match any meter are still stored — they’ll be picked up if a matching meter is created later.

Best Practices

High-Volume Ingestion

For bulk imports or high throughput, use POST /v1/events/batch to send up to 10,000 events per request.
Buffer events in your application and flush every few seconds rather than sending one at a time.
const buffer = [];

const queueEvent = (event) => buffer.push(event);

setInterval(async () => {
  if (buffer.length === 0) return;
  const events = buffer.splice(0, 100);
  await sendBatch(events);
}, 5000);
Don’t block your application waiting for the Events API. Send asynchronously and handle failures in the background.

Reliability

Include idempotencyKey when implementing retry logic to prevent duplicate billing.
On transient failures (5xx errors), retry with increasing delays: 1s, 2s, 4s, etc.
4xx errors (validation, auth) won’t succeed on retry. Log them and investigate.

Data Quality

Add context that helps with debugging, analytics, and future billing flexibility.
Event names are case-sensitive. api_callAPI_CALLapiCall.
Real-time events give customers accurate usage visibility and prevent billing surprises.

Next Steps

Send Your First Event

Step-by-step guide to sending events

Events API Reference

Full API specification

List Events

Query and paginate through events

Batch Events

Send multiple events at once

Meters

Learn how meters aggregate events