Contact avatars
Why we don’t auto-pull WhatsApp profile pictures
Meta’s WhatsApp Cloud API does not expose end-user profile pictures. Themessages webhook delivers a contacts[].profile payload that only includes
name. The profile_picture_url field exists only on
/whatsapp_business_profile — i.e. the business’s own profile — not on the
end-user contacts messaging you.
This is consistent across every BSP on the platform (360dialog, Twilio, Infobip,
etc.). There is no public API, Graph edge, or webhook field that returns the
customer’s avatar.
What we render
- Initials fallback (default). We render the contact’s initials on a
deterministic colour swatch derived from a hash of the contact’s
wa_id(fallback to phone number, fallback to name). The same contact always receives the same colour across sessions and devices. - Operator-uploaded override. Operators can upload a JPG / PNG / WEBP up
to 2 MB from the inbox contact details panel. The image is re-encoded to a
square 256×256 JPG (EXIF stripped) and stored in the
workspace-attachmentsobject store undercontacts/{workspaceId}/{contactId}/avatar-{hash}.jpg. The URL is persisted inpublic.contacts.avatar_urland rendered everywhere the contact appears (inbox sidebar, inbox header, contacts list, mobile layouts).
Operator workflow
- Open a conversation → right-hand details panel → Profile section → Upload / Replace / Remove buttons.
- Uploads hit
POST /api/contacts/[contactId]/avatar(multipart/form-data,filefield); removal hitsDELETE /api/contacts/[contactId]/avatar. - Both routes require an active operator workspace access check and verify that the contact belongs to the caller’s workspace before mutating.
Data model
avatar_url is nullable. Null means “render the initials fallback”.
Shared component
@repo/design-system/components/contact-avatar exposes <ContactAvatar> with
size of xs, sm, md, lg. Use it anywhere a contact is listed to keep
the fallback behaviour consistent.