Every event carries a prompt_ready string — a short text rendering of what happened, rendered server-side, deterministic, and capped at 4 KB. Feed it directly to a model instead of hand-writing prompts for every provider your agent touches.
What it looks like
The same underlying transformation, applied per provider. Three examples:
Stripe — invoice.payment_succeeded
Raw webhook:
{
"id": "evt_1NxYzabc",
"type": "invoice.payment_succeeded",
"data": {
"object": {
"id": "in_1PabcD",
"customer": "cus_AbcDef",
"amount_paid": 12000,
"currency": "usd",
"status": "paid"
}
}
}
event.prompt_ready:
Stripe invoice.payment_succeeded at 2026-04-19T12:34:56Z.
channel: billing-inbox (webhook)
Invoice in_1PabcD paid by customer cus_AbcDef.
Amount: $120.00 USD.
Status: paid.
GitHub — pull_request.opened
event.prompt_ready:
GitHub pull_request opened at 2026-04-19T09:15:22Z.
channel: nevo-prs (webhook)
Repository: nevo-sh/sdk-python
PR #42: "Add retry support to HTTP client" by alice.
Branch feat/retries → main.
Stats: +180 / -23 across 7 files.
Forwarded email
event.prompt_ready:
Email from alice@acme.com to support@in.nevo.sh.
Subject: Re: API limits.
Received 2026-04-19T08:22:04Z.
Body:
can we bump our monthly quota? we're hitting the 100k ceiling.
Why it exists
Without prompt-ready, code that feeds inbound events to a language model looks like:
if event.type == "webhook.received" and event.channel.type == "webhook":
provider = detect_provider(event.webhook.headers)
if provider == "stripe":
payload = json.loads(event.webhook.body)
if payload["type"] == "invoice.payment_succeeded":
prompt = f"A Stripe invoice was paid: ${payload['data']['object']['amount_paid'] / 100:.2f} from customer {payload['data']['object']['customer']}…"
elif payload["type"] == "customer.subscription.updated":
prompt = f"…"
...
elif provider == "github":
...
With prompt-ready:
prompt = event.prompt_ready
That’s the whole thing. The switch, the parser, the per-event string template — all gone.
Properties
- Deterministic. Same event in, same text out. No model in the loop, no drift.
- Bounded. Capped at 4 KB. Large payloads are summarised, not pasted verbatim; the full raw payload is always on
event.data. - Versioned.
prompt_ready_versionon every event identifies which renderer generation produced the text — useful if you cache prompts or want to re-render old rows after a renderer improvement. - Per-provider. We ship first-party renderings for Stripe, GitHub, Linear, and email today. Anything else falls back to a generic webhook rendering that still reads cleanly.
When to render your own
When the default rendering misses something your agent cares about, ignore prompt_ready and build a prompt from event.data. The raw payload is untouched; nothing’s lost.
When the default rendering is wrong for everyone — let us know. Per-provider fixes ship fast.