Skip to main content

media.delivery.failed

Composio upload of listing media to the agent's cloud storage failed after exhausting all retries. NO files were uploaded (for partial-success, subscribe to media.delivery.completed instead - the summary there carries files_failed counts).

Category: Media

Emitted when: deliver_media_to_storage Celery task raised its terminal exception after retries exhausted (app/tasks/composio_tasks.py).

Envelope

Every delivery wraps the event-specific data block in the canonical envelope documented on the webhook overview page. The body carries SIX keys; the HMAC signature is delivered as the X-Webhook-Signature request header on the inbound POST to your endpoint (NOT as a body field; including it in the body would make the HMAC a function of its own output).

FieldTypeDescription
event_idstringULID; globally unique. Safe to persist as the dedup key.
event_typestringAlways media.delivery.failed for this event.
api_versionstringPayload schema version (YYYY-MM-DD).
timestampintegerUnix epoch seconds at dispatch time.
noncestringULID; unique per delivery, used for replay rejection.
dataobjectEvent-specific payload (see below).

Delivery also sets three HTTP headers on the inbound POST:

HeaderMeaning
X-Webhook-Event-IdMirrors event_id; cheaper than parsing the body to dedup.
X-Webhook-TimestampMirrors timestamp for replay-window checks.
X-Webhook-Signaturesha256=<hex> HMAC over timestamp.body.

Data schema

FieldTypeRequiredDescription
agency_idstringyesAgency Id
listing_numberstringyesPublic listing number, 7-char lowercase nanoid ([a-z0-9]{7})
job_idstringyes'deliver_' - same id the would-be .completed event would have carried; lets integrators correlate.
destinationobjectyesWhere the upload was TARGETED (even though it failed). Mirrors the DeliveryDestination in media_delivery_completed by design: subscribers that parse both events should be able to use the same destination deserializer for either variant. The classes live in separate modules to keep each event file independently importable (the app/events/data_models/__init__.py eager-import pattern does not assume cross-module imports between event files).
errorobjectyesNormalized error block. code is always a stable short string from the caller-side whitelist (e.g. "composio_401", "composio_rate_limited", "composio_5xx", "composio_unknown"). Integrators branch on the code, not the message - which is locale / version specific and subject to drift. message is a truncated (<= 1024 chars) human-readable description of the underlying failure. Truncation prevents a pathological exception message from bloating the webhook envelope beyond what the dispatcher's size limits accept.
duration_secondsintegeryesDuration Seconds

Sample payload

The sample below is a live fixture. Feed the exact body bytes shown plus the timestamp into the HMAC-SHA256 recipe from the webhook overview using the documented test-vector secret (test_secret_001 ) and you will recover the signature header value printed below.

Body (delivered as the HTTP request body):

{
"event_id": "evt_1MZQKGSKEZ7F23VNSPJ066DR0C",
"event_type": "media.delivery.failed",
"api_version": "2026-04-17",
"timestamp": 1745339401,
"nonce": "63M1E1KPHN1KWX0N6AEJZW364M",
"data": {
"agency_id": "user_01HXAGENCY0000000000000",
"listing_number": "abc1234",
"job_id": "01HX5Y7Z2M3N4P5Q6R7S8T9U0V",
"destination": {
"kind": "google_drive",
"folder_path": "/Listings/123-Main-St-Portland/Photos"
},
"error": {
"code": "media.delivery.failed.bucket_403",
"message": "Destination bucket rejected upload: 403 Forbidden on PUT."
},
"duration_seconds": 0
}
}

Signature header (delivered as X-Webhook-Signature on the same request):

sha256=c8b602b2ec1f4cded09036aab88c212ea9a07efa5c5163c71ba180728199a8a6

Retry behaviour

Failed deliveries retry on the sequence 2, 4, 8, 16, 32 seconds (five attempts plus the initial call for a total of six over approximately 62 seconds of wall clock). After the final attempt the delivery lands in the dead-letter queue and the dashboard operator can replay it from Settings > API Keys > Webhook Endpoint > Deliveries. See the webhook overview for the full rules.

Verification

Every receiver MUST verify the X-Webhook-Signature header using the recipe in the webhook overview. Rejecting deliveries whose timestamp is more than 5 minutes off your wall clock (after NTP correction on your side) or whose nonce has already been consumed in the last 10 minutes is part of the contract.