> ## Documentation Index
> Fetch the complete documentation index at: https://docs.switchbord.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Contact avatars

> How contact profile pictures are rendered and managed in inbox + contacts.

# 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

```sql theme={null}
alter table public.contacts add column if not exists avatar_url text;
```

`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.
