SDK ingest — who Herald accepts events from
Herald rejects SDK events whose actor email isn’t on the product’s allowed-senders list. The list is on by default — when a product is created, every member of the owning organization is added automatically. Stranger events 422; your team’s events flow through.
What gets checked
Section titled “What gets checked”Each ingest route reads one or more email fields off the payload and gates them against the product’s allowed-senders list before validation runs.
| Route | Field checked |
|---|---|
POST /v1/ingest/feedback | payload.author.email |
POST /v1/ingest/feedback:batch | payload.items[*].author.email |
POST /v1/ingest/conversation | payload.participants[*].email |
POST /v1/ingest/sales-call | payload.participants[*].email |
POST /v1/ingest/revenue | payload.customer.email |
Within a batch, addresses are de-duplicated before checking — sending the same author 1,000 times costs one allowlist lookup, not 1,000.
What you get back
Section titled “What you get back”A non-allowed actor returns a structured 422 with the offending address:
HTTP/1.1 422 Unprocessable EntityContent-Type: application/json
{ "error": "sender_not_allowed", "address": "alice@bloomflow.example" }The first miss in a batch is the deal-breaker — the rest of the items aren’t checked. Fix the address (or add it to the allowlist) and retry.
Add a sender
Section titled “Add a sender”Open the dashboard → Settings → Allowed senders. Add either:
- An address —
alice@bloomflow.example. Exact match, lowercased. - A domain —
bloomflow.example. Covers every address at that domain.
Changes take effect immediately. The next event from that address goes through.
When the system can’t check
Section titled “When the system can’t check”Under a region-level Cloudflare incident the gate can’t reach the product’s Durable Object. In that case the route returns 503:
HTTP/1.1 503 Service UnavailableContent-Type: application/json
{ "error": "service_unavailable" }Retry with backoff. Herald never fails open — a 503 means the check didn’t run, not that the event was accepted. The check is per-product and runs on every event, so under sustained load you’ll see 503s on a small fraction of requests during an incident, not on all of them.
Why default-deny
Section titled “Why default-deny”The same gate runs on the email forward path: anything sent to fwd+{product_id}@inbox.withherald.co from an address that isn’t on the list bounces with a Herald-voice reply and never touches R2. Default-deny is the load-bearing security guarantee that lets Herald promise one product → one Durable Object → no cross-tenant leakage. Allowing the SDK to push events for arbitrary addresses would make the email gate a Maginot Line.
If a customer needs Herald to track their own users’ events, the customer’s domain goes on the allowlist — not every random address that happens to ship in a payload.