media.updated
An incoming attachment is ready - PaalChat downloaded it from Meta and stored it
(status: stored, with a 15-minute signed url) - or it could not be fetched
(status: failed, with error). Follows the message's message.received, which carries
its media_id. Only for businesses with media enabled.
Verify every callback
Check X-PaalChat-Signature on the raw body, reject timestamps older than 5 minutes, and de-duplicate on X-PaalChat-Delivery. Answer 2xx within 10 seconds. See Webhooks and callbacks.
Headers
X-PaalChat-Event
string
X-PaalChat-Delivery
string
Unique per event (equals body id). De-duplicate on it.
Format uuid.
X-PaalChat-Signature
string
t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>. Reject if invalid or older than 300 seconds.
Pattern ^t=\d+,v1=[a-f0-9]{64}$.
Body
id
string
Equals X-PaalChat-Delivery.
Format uuid.
sandbox
boolean
true for sandbox businesses (test keys) - nothing reached WhatsApp.
event
any
Always media.updated.
occurred_at
string
Format date-time.
business
object
external_id
string
business.external_id
data
object
media
object
data.media
id
integer
data.media.id
direction
string
data.media.direction
incoming: a customer's attachment; outgoing: your upload.
One of: incoming, outgoing.
status
string
data.media.status
pending: being fetched from Meta; deleted: removed after the retention period.
One of: pending, stored, failed, deleted.
mime_type
string | null
data.media.mime_type
filename
string | null
data.media.filename
size
integer | null
data.media.size
Bytes.
sha256
string | null
data.media.sha256
Hex SHA-256 of the file.
url
string | null
data.media.url
Signed link to the bytes (stored only). No token needed; expires at url_expires_at.
url_expires_at
string | null
data.media.url_expires_at
Format date-time.
error
string | null
data.media.error
Why the file could not be fetched.
created_at
string
data.media.created_at
Format date-time.
stored_at
string | null
data.media.stored_at
Format date-time.
message_id
integer | null
data.message_id
The incoming message the file came with.
conversation_id
integer | null
data.conversation_id