Skip to main content

Contact avatars

Why we don’t auto-pull WhatsApp profile pictures

Meta’s WhatsApp Cloud API does not expose end-user profile pictures. The messages 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

  1. 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.
  2. 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-attachments object store under contacts/{workspaceId}/{contactId}/avatar-{hash}.jpg. The URL is persisted in public.contacts.avatar_url and 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, file field); removal hits DELETE /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.