Granular Outcome Codes for Delivery Attempts
The per-attempt observability launch in May gave every delivery attempt a structured outcome code. This update makes those codes considerably more precise: outcome codes now reflect the destination's actual response, seven new codes cover the most common rejection modes, and failed attempts keep the provider's own error detail for inspection.
Seven new outcome codes
The outcome vocabulary grows from nine to sixteen codes. The new additions distinguish rejection modes that previously landed in the generic client-error bucket:
invalid_request— the destination rejected the request as malformedvalidation_failed— the destination rejected one or more field valuesdestination_not_found— the target resource no longer exists at the destinationdestination_conflict— the request conflicted with current destination statedestination_gone— the destination reported it is permanently gonedestination_timeout— the destination reported a request timeoutpayload_too_large— the destination refused the payload size
These join the existing delivered, rate_limited, auth_failed, timeout, network_error, mapping_error, rejected_client_error, rejected_server_error, and internal_error codes. The attempt drill-down on the event detail page renders all sixteen.
Codes that reflect the destination's response
Outcome codes previously reported the broad category of a failure, so many distinct rejections looked the same. They now reflect the destination's actual HTTP response — the real status code plus the provider's own error code. A missing HubSpot list shows as destination_not_found, a rejected field value as validation_failed, a throttled request as rate_limited, and so on.
Every integration maps its destination's responses to the same shared vocabulary, so a rate_limited from HubSpot means the same thing as a rate_limited from a custom webhook — one set of codes to build dashboards and alerting against, regardless of where the event was headed.
Provider error detail on failed attempts
When a delivery fails, the attempt now records a structured response: the HTTP status, the provider's own error code (for example, the error category HubSpot returns), and the destination's response body. The response details view in the attempt drill-down shows exactly what the destination said, so debugging a rejected delivery no longer requires reproducing the call yourself. Response details remain permission-gated the same way as before.