WhatsApp Web API client for Elixir — a behaviour-accurate port of Baileys that brings the full WhatsApp multi-device protocol to the BEAM.
BaileysEx connects directly to WhatsApp's WebSocket-based protocol. No browser automation, no headless Chrome, no external Node.js sidecar. Just a supervised Elixir process tree that handles the Noise handshake, Signal Protocol encryption, binary XMPP framing, and the complete WhatsApp feature surface — natively.
Note: This library communicates with WhatsApp's servers using a reverse-engineered protocol. It is not affiliated with, authorized by, or endorsed by WhatsApp or Meta. Use responsibly and in accordance with WhatsApp's Terms of Service.
- QR code pairing — scan from WhatsApp mobile to link a new device
- Phone number pairing — enter a numeric code instead of scanning
- Persistent sessions — save credentials to disk and reconnect without re-pairing
- Multi-device protocol — operates as a linked companion device
- End-to-end encryption via the Signal Protocol (pure Elixir implementation)
- 27+ message types — text, images, video, audio, documents, stickers, contacts, location, polls, reactions, forwards, edits, deletes, and more
- Quoted replies and mentions — reply to and reference specific messages
- Delivery tracking — delivery receipts, read receipts, played receipts
- Link previews — attach URL preview metadata to outbound messages
- Upload and download — images, video, documents, audio, stickers, GIFs, voice notes
- Streaming architecture — encrypted media with AES-256-CBC + HMAC, never loads entire files into memory
- Automatic key management — HKDF-derived media keys with type-specific info strings
- Stale URL refresh — re-request expired media download URLs from paired devices
- Full group lifecycle — create, update subject/description, leave, delete
- Participant management — add, remove, promote, demote
- Invite flows — v3/v4 invite codes, join-by-link, join approval
- Community support — create communities, link/unlink subgroups, manage metadata
- Group settings — ephemeral messages, announcement mode, membership approval
- Online/offline/typing indicators — send and subscribe to presence updates
- Profile management — get/set profile pictures, push names, status text
- Contact validation — check which phone numbers are registered on WhatsApp
- Business profiles — fetch and manage business profile data, catalogs, collections
- Subscribe/unsubscribe — follow and unfollow WhatsApp channels
- Create and manage — create, update, and delete newsletters
- Interact — mute, react, and fetch newsletter content
- Cross-device sync — archive, mute, pin, star, and read state synced across linked devices
- LTHash integrity — rolling hash verification detects drift or tampering before applying patches
- Full Syncd codec — encode and decode WhatsApp's versioned app state patch format
- WAM encoding — build and send WhatsApp Analytics and Metrics buffers for Baileys wire parity
BaileysEx isn't just a translation — it's a reimagining of Baileys for a runtime built for exactly this kind of work:
| Baileys (Node.js) | BaileysEx (Elixir/OTP) |
|---|---|
| Callbacks and Promises | Supervised process trees with :rest_for_one restart strategy |
async-mutex library |
BEAM processes — concurrency is the runtime, not a library |
| Manual reconnect logic | :gen_statem state machine with automatic reconnection |
node-cache / lru-cache |
ETS tables — concurrent, lock-free, built into the VM |
pino logging |
Logger with structured metadata and configurable backends |
| Single-threaded event loop | Preemptive scheduling across all available cores |
| Process crash = app crash | Supervisor restarts failed children — let it crash |
Crypto is native too. All cryptographic primitives (AES, HMAC, SHA, PBKDF2,
Curve25519, Ed25519) use Erlang's built-in :crypto module — no external C/Rust
for standard operations. Rust NIFs are used only for the Noise Protocol framework
(snow) and XEdDSA signing (curve25519-dalek), where no Erlang/Elixir
equivalent exists.
Add baileys_ex to your mix.exs dependencies:
def deps do
[
{:baileys_ex, "~> 0.1.0-alpha.12"}
]
endRequirements: Elixir 1.19+, OTP 28, Rust toolchain (for NIF compilation).
BaileysEx.Auth.NativeFilePersistence.use_native_file_auth_state/1is the recommended default for Elixir-first apps. It stores credentials and Signal keys in durable ETF files with crash-safe writes.BaileysEx.Auth.FilePersistence.use_multi_file_auth_state/1keeps the Baileys-compatible multi-file JSON layout when you need that on-disk contract. Treat it as a compatibility bridge for teams migrating off a Baileys JS sidecar, not as the long-term Elixir-first default.- Custom SQL/NoSQL backends remain supported through
BaileysEx.Auth.PersistenceandBaileysEx.Signal.Store. Advanced custom store modules must implementSignal.Store.transaction/3with the explicit transaction-scoped handle passed into the callback; built-in users do not need to care about this.
alias BaileysEx.Auth.NativeFilePersistence
alias BaileysEx.Connection.Transport.MintWebSocket
# Load or create auth state
{:ok, persisted_auth} =
NativeFilePersistence.use_native_file_auth_state("tmp/baileys_auth")
parent = self()
connection = MyApp.WhatsApp
# Start the connection under your application's supervisor.
connection_child =
{BaileysEx,
Keyword.merge(persisted_auth.connect_opts,
auth_state: persisted_auth.state,
name: connection,
transport: {MintWebSocket, []},
on_qr: fn qr -> send(parent, {:qr, qr}) end,
on_connection: fn update -> send(parent, {:connection_update, update}) end
)}
children = [
Supervisor.child_spec(connection_child, id: connection)
]
{:ok, _supervisor} =
Supervisor.start_link(children,
strategy: :one_for_one,
name: MyApp.Supervisor
)
# Persist credentials on update
BaileysEx.subscribe_raw(connection, fn events ->
if Map.has_key?(events, :creds_update) do
{:ok, latest} = BaileysEx.auth_state(connection)
:ok = persisted_auth.save_creds.(latest)
end
end)
# Wait for the connection to open
receive do
{:connection_update, %{connection: :open}} -> :connected!
after
30_000 -> raise "timed out waiting for connection"
endUse BaileysEx.connect/2 for caller-owned scripts, tests, and IEx sessions. A
long-lived application connection should use the child specification above so
the host owns startup, shutdown, and restart policy. Give every connection a
unique child :id when supervising more than one.
If you specifically need Baileys-compatible JSON auth files during a sidecar
migration or compatibility rollout, swap in
BaileysEx.Auth.FilePersistence.use_multi_file_auth_state/1.
{:ok, _sent} =
BaileysEx.send_message(connection, "15551234567@s.whatsapp.net", %{
text: "Hello from BaileysEx!"
})BaileysEx.subscribe(connection, fn
{:message, msg} -> IO.inspect(msg, label: "incoming")
{:connection, update} -> IO.inspect(update, label: "connection")
_other -> :ok
end){:ok, _sent} =
BaileysEx.send_message(connection, "15551234567@s.whatsapp.net", %{
image: {:file, "photo.jpg"},
caption: "Sent from Elixir"
})Full guides and API reference are available on HexDocs.
| Section | What you'll learn |
|---|---|
| Installation | Prerequisites, dependency setup, compilation |
| First Connection | QR pairing, phone pairing, credential persistence |
| Send Messages | Text, replies, reactions, polls, forwards, edits |
| Media | Upload images/video/docs, download, stale URL refresh |
| Groups | Create, manage participants, invite flows, communities |
| Events | Subscribe to connection, message, and presence events |
| Authentication | Custom credential storage, Signal key management |
| Configuration | All connection and runtime options |
| Event Catalog | Every event type with payload shapes |
| Message Types | Complete message payload reference |
A complete echo bot is included at examples/echo_bot.exs:
mix run examples/echo_bot.exs -- --helpSee the Echo Bot guide for a walkthrough.
BaileysEx emits Telemetry events under the
[:baileys_ex] prefix — connection lifecycle, message send/receive, media
upload/download, and NIF operations. Attach your handlers for dashboards,
alerting, or tracing.
BaileysEx is in alpha. The API surface is stabilizing but may change before
1.0. The library tracks Baileys v7.0.0-rc14 as its upstream reference for wire
behaviour and feature scope.
- Baileys — the TypeScript original that defines the protocol behaviour BaileysEx implements
- whatsmeow — Go implementation, referenced for protocol details
- whatsapp-rust — Rust implementation, referenced for documentation patterns
MIT — see LICENSE for details.