Submit an event

Submits a signal to an outcome. The request body and token are validated immediately, then the event is enqueued for asynchronous processing. Outcome creation, identity checks, condition evaluation, and state transitions happen in a background consumer with automatic retries.

The first event with a previously-unseen key creates the outcome by snapshotting the agent's contract. agent_key is optional. If you only run one agent, or your action maps to a single agent, you can leave agent_key out and Done will pick the right one. Send it to be explicit, or to resolve a session Done could not route on its own. Once an outcome exists, later events for the same key route to it automatically, so they need no agent_key at all. Events for CONFIRMED or FAILED outcomes are rejected as OUTCOME_NOT_OPEN.

Returns 202 Accepted on success.

The request body is capped at 256 KiB. Larger bodies are rejected with 413 PAYLOAD_TOO_LARGE. Keep properties small and store large metadata (documents, transcripts, images) in your own storage, sending only a reference.

Example

curl -X POST https://api.thewitn.com/v1/events \
  -H "Authorization: Bearer $WITN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "support:ticket:1001",
    "action": "csat_received",
    "agent_key": "support",
    "customer_key": "acme",
    "properties": {
      "value": 4,
      "attribution": 0.8,
      "settles_at": "2024-01-18T10:00:00Z"
    }
  }'

properties.value is the field the outcome evaluator reads. Use it for billable condition checks:

{ "fact": "csat_received", "operator": "gte", "value": 3 }

properties.value is required for match and comparison leaves:

{ "fact": "csat_received", "operator": "match", "value": "approved" }

properties.attribution is used only for billing attribution (first, last, min, max, sum). It is evaluated at settlement time, not during outcome transitions.

properties.settles_at optionally overrides the next settlement timestamp. Use it when an event carries its own business deadline, such as an appointment time plus a grace period. It must be an ISO datetime string.

Any other fields inside properties are stored in the event log as metadata and ignored by the evaluator.

Idempotency

idempotency_key is optional. Send one to make retries safe. The same key on the same outcome always maps to the same event, so if a request times out after the server already accepted it, you can retry with the same key and it will not create a second billable event.

Keys are scoped to your account and outcome key, so two accounts or two outcomes can use the same idempotency key without clashing. Reusing an idempotency key on the same outcome with a different body keeps the first event and drops the later one. Leave the key out and every request is treated as a new event.

curl -X POST https://api.thewitn.com/v1/events \
  -H "Authorization: Bearer $WITN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "support:ticket:1001",
    "action": "csat_received",
    "customer_key": "acme",
    "idempotency_key": "csat-1001-attempt-1"
  }'

Multiple events with the same action

When you submit more than one event with the same action, there are two separate rules:

FieldRule
properties.valueUsed for condition evaluation. The evaluator checks the latest event for that action.
properties.attributionUsed for billing. The agent's attribution_method setting controls which value is used: first, last, min, max, or sum.
properties.settles_atOverrides the next settles_at for the outcome. If omitted, witn uses the event timestamp plus the agent's settlement_period.

Concurrent submissions

Concurrent submissions return 202 Accepted once they are enqueued. If the first event advances the outcome before the second is processed, the background consumer discards the second event because the outcome is already terminal:

const res = await fetch('https://api.thewitn.com/v1/events', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.WITN_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    key: 'support:ticket:1001',
    action: 'csat_received',
    agent_key: 'support',
    customer_key: 'acme',
  }),
})

if (res.status === 202) {
  // The event was accepted for async processing.
  // Inspect the outcome later if you need the final state.
}

Errors

CodeStatusWhen
VALIDATION_ERROR400key, action, or customer_key is missing or invalid, or agent_key is present but empty.
TOKEN_INVALID401The token is missing or not recognised.
PAYLOAD_TOO_LARGE413The event body exceeded the 256 KiB ingest limit.

The background consumer can also surface these errors in the DLQ (the HTTP endpoint still returns 202):

CodeWhen
AGENT_NOT_FOUNDFirst event for a new key references a agent that does not exist for your account.
CUSTOMER_NOT_FOUNDcustomer_key does not match any customer for your account.
RATE_CARD_NOT_FOUNDThe customer has no rate card assigned.
RATE_CARD_ENTRY_NOT_FOUNDThe customer's rate card has no entry for the agent.
OUTCOME_IDENTITY_MISMATCHA later event for the same key carries a different agent_key or customer_key.
OUTCOME_NOT_OPENThe outcome is already CONFIRMED or FAILED.

On this page