Every inbound event — webhook, email, future source — reaches your service as the same object: a Nevo Event. Branching on the single type field is how you route by kind.
Schema
{
"id": "evt_01hk8…",
"schema": "nevo.event.v1",
"type": "webhook.received",
"origin": "live",
"created_at": "2026-04-17T19:22:04.123Z",
"project_id": "…",
"channel": { "id": "…", "label": "stripe events", "type": "webhook" },
"data": { /* source-specific payload */ },
"prompt_ready": "Stripe invoice.paid — $42.00 from acme.co. …",
"prompt_ready_version": 2
}
Fields
| Field | Type | Notes |
|---|---|---|
id | string | ULID. Stable across live + replay deliveries. |
schema | string | nevo.event.v1 today. Bumps on breaking changes. |
type | string | webhook.received or email.received. |
origin | string | live or replay. Use for idempotency. |
created_at | ISO 8601 | Server-side ingest time. |
project_id | UUID | The Nevo project this event belongs to. |
channel | object | { id, label, type } — which channel received the event. |
data | object | Source-specific payload. Shape varies by type (below). |
prompt_ready | string | LLM-friendly text rendering of the event. See prompt-ready. |
prompt_ready_version | int | Renderer version stamp. Bumps when we change the renderer. |
data shape per type
webhook.received
{
"method": "POST",
"path": "/v1/webhook/abc…",
"headers": { "content-type": "application/json", "stripe-signature": "…" },
"body_encoding": "json",
"body": { /* parsed JSON body when Content-Type was JSON */ },
"body_raw_b64": "",
"body_size_bytes": 512
}
body_encoding tells you where the body lives:
json→ parsed payload inbodytext→ raw string inbodybase64→ original bytes base64-encoded inbody_raw_b64
email.received
{
"resend_email_id": "re_…",
"message_id": "<abc@mail.example>",
"from": "alice@acme.com",
"to": ["support@in.nevo.sh"],
"matched_address": "support@in.nevo.sh",
"cc": [],
"bcc": [],
"subject": "Re: API limits",
"text": "can we bump our monthly quota?",
"html": "<p>…</p>",
"attachments": [{ "filename": "usage.csv", "content_type": "text/csv", "size_bytes": 1024 }],
"received_at": "2026-04-17T19:22:03.999Z"
}
Live vs replay
origin is live for freshly-ingested events and replay when an operator re-delivered a historical event from the dashboard.
Delivery
The same event object is pushed to two places:
- SDK clients connected for the project.
- HTTP endpoints the project has configured, signed with the channel’s secret.
Either failing doesn’t fail the other. A handler that takes five minutes to process doesn’t block other events — each event gets its own task.