When an application publishes a custom event to Amazon EventBridge with the PutEvents API, what does an event entry contain, and why can a call that returns HTTP 200 still have lost events?
answer
- three fields the publisher sets
- payload travels as a string
- batch call, not atomic
- 200 can still hide rejections
- FailedEntryCount
basics
~20 sA PutEvents entry carries Source, DetailType and Detail, plus optional EventBusName, Resources and Time. Because PutEvents is a batch call, a 200 response can still report FailedEntryCount above zero, and those rejected entries are lost unless the publisher resends them.
solid answer
~40 sYou send EventBridge a batch of entries. Each entry has three fields that matter: `Source` (who emitted it, e.g. `com.acme.orders`), `DetailType` (the event name, e.g. `OrderPlaced`) and `Detail` (a JSON string payload). Optionally you set `EventBusName` — omit it and the event goes to the `default` bus — plus `Resources` and `Time`. EventBridge then wraps it in a delivered envelope adding `id`, `account`, `region`, `version`, and normalising your fields to `source`, `detail-type` and `detail`, which is the shape rules match against. The gotcha is that PutEvents is a partial-failure batch API, like Kinesis: the HTTP call succeeds while individual entries fail. You must read `FailedEntryCount` and inspect per-entry `ErrorCode`/`ErrorMessage`, and retry just the failed ones. Code that only checks the HTTP status silently drops events.
code
bash · 1 lineaws events put-events --entries '[{"Source":"com.acme.orders","DetailType":"OrderPlaced","Detail":"{\"orderId\":\"o-123\",\"total\":42.5}","EventBusName":"orders-bus"}]'go deeper
Know the three fields you always set — Source, DetailType and Detail — and that Detail is a JSON string. Say plainly that the delivered event has extra fields like id, account and region that EventBridge adds.
Explain the mapping between the PascalCase request entry and the lowercase delivered envelope, and why rules match on detail-type and source. Be ready to describe the partial-failure contract of the batch API.
Show the production habit: inspect FailedEntryCount, retry only the failed positions with backoff, and alarm on a non-zero rate so a silent leak surfaces. Mention the claim-check pattern for oversized payloads.
Own Source and DetailType as an organisation-wide contract — naming, ownership and payload versioning — because every downstream rule couples to them. Be able to argue what belongs in the event versus behind a claim check.
## The two shapes of an EventBridge event There are two representations of the same event and confusing them is the most common beginner mistake. The first is the **entry you publish**. `PutEvents` takes an `Entries` array; each element is a `PutEventsRequestEntry` with PascalCase fields: - `Source` — a string identifying the emitter. AWS's own events use reverse-DNS values such as `aws.s3`; your applications should use something you own, like `com.acme.orders`. Rules match on it constantly, so treat it as a stable contract, not a log tag. - `DetailType` — the event's name, e.g. `OrderPlaced`. Together with `Source` this is the pair that consumers subscribe to. - `Detail` — the payload, passed as a **JSON string**, not a nested object. Forgetting to serialise it is the classic first error. - `EventBusName` — the target bus. Omitted, the event lands on the `default` bus of the caller's account and region. - `Resources` — an optional array of ARNs the event concerns; rules can match on it. - `Time` — an optional business timestamp; if you omit it EventBridge stamps ingestion time. - `TraceHeader` — an optional X-Ray trace header to keep a trace stitched together. The second is the **envelope delivered to targets**, which is lowercase/kebab-case JSON: ```json { "version": "0", "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718", "detail-type": "OrderPlaced", "source": "com.acme.orders", "account": "111122223333", "time": "2025-03-04T12:00:00Z", "region": "eu-west-1", "resources": [], "detail": { "orderId": "o-123", "total": 42.5 } } ``` EventBridge generates `id`, `account`, `region` and `version`; you never supply them. Rules and input transformers see this shape, which is why an event pattern says `"detail-type"` and a JSONPath in a transformer says `$.detail.orderId`. ## Batching and partial failure `PutEvents` accepts multiple entries in one call, which is what makes it cheap to use from a hot path. The price of batching is that the call is **not atomic**. EventBridge returns HTTP 200 with a body containing `FailedEntryCount` and an `Entries` array positionally aligned with your request. A successful element carries an `EventId`; a failed one carries `ErrorCode` and `ErrorMessage` instead — typically a throttling or validation error. So the correct client loop is: call, check `FailedEntryCount`, and if it is non-zero rebuild a batch from exactly the failed positions and retry with backoff. An SDK's built-in retry logic does **not** cover this, because from HTTP's point of view nothing went wrong. Teams discover this months later when a report is short a few thousand events with no error in any log. Also note that entry size is bounded (256 KB per entry as of 2025, counted over the whole entry, not just `Detail`), and that entries which are too large are rejected the same way. This is the practical reason for the **claim-check** pattern: write the big payload to S3 and publish an event carrying the object key rather than the blob. ## What PutEvents does not promise - **No ordering.** Two events published in sequence may be delivered to a target in either order. If order matters, carry a version or sequence number in `detail` and let the consumer decide. - **No deduplication.** There is no message-deduplication ID; combined with at-least-once target delivery, consumers must be idempotent. - **No subscriber feedback.** A successful `PutEvents` means EventBridge accepted the event, not that any rule matched it or any target received it. An event that matches nothing is simply dropped, and that is normal for a bus — publishers are decoupled from subscribers by design. ## Practical checklist Serialise `Detail` to a string; keep `Source`/`DetailType` stable and versioned in the payload; set `EventBusName` explicitly rather than relying on the default; always inspect `FailedEntryCount`; emit a metric on failed entries so a slow leak is visible; and keep payloads small enough that you are nowhere near the entry limit.
- Your event payload occasionally exceeds the entry size limit. What do you change?Stop shipping the blob. Write the large body to S3 and publish an event whose `detail` carries the bucket and key plus enough identifiers for routing — the claim-check pattern. Consumers fetch on demand, most of which never need the full payload. It also keeps rule patterns cheap and makes archives far smaller.
- A successful PutEvents call returned no error, but no consumer reacted. Where do you look first?Acceptance is not delivery. Check that the event went to the bus you think it did — omitting `EventBusName` sends it to `default`. Then check whether any rule matched: an event matching no rule is discarded silently. Compare the exact envelope field names against the rule's pattern, since `DetailType` becomes `detail-type` on delivery.
- Does EventBridge guarantee ordering between two events published one after the other?No. EventBridge makes no ordering guarantee, and target delivery is at-least-once, so a consumer can see events out of order or twice. If order matters, put a monotonically increasing version or a business timestamp in `detail` and have the consumer discard stale updates, or move the ordered part of the workload onto a stream or a FIFO queue.
saying these in an interview costs you the question
- Assuming a 200 response means every entry was published
- Passing Detail as a nested JSON object instead of a string
- Believing EventBridge preserves publish order
- Thinking PutEvents confirms a consumer received the event
- Expecting EventBridge to deduplicate identical events