# WhatsApp SDK for Elixir
[](https://hex.pm/packages/whatsapp_sdk)
[](https://github.com/jeffhuen/whatsapp_sdk/actions/workflows/ci.yml)
Comprehensive Elixir SDK for the [WhatsApp Business Platform Cloud API](https://developers.facebook.com/docs/whatsapp/cloud-api),
generated from Meta's official [OpenAPI spec](https://github.com/facebook/openapi) with full API coverage.
> **Note:** This is not an official Meta SDK. Meta does not publish a
> first-party Elixir library. This project is generated from Meta's
> [OpenAPI spec](https://github.com/facebook/openapi) for the WhatsApp
> Business Platform, follows the same API surface, and is tested for
> parity against the spec. The goal is an idiomatic Elixir experience
> with complete API coverage.
### What's Included
The **SDK layer** provides typed resource structs, dedicated service modules,
and auto-paging pagination — all generated from the spec with full
documentation. The **client layer** handles HTTP execution via Finch with
connection pooling, automatic retries, request encoding, response
deserialization, and telemetry.
Together, the full Cloud API surface is covered: 79 service modules,
352 typed resource structs, 113 API operations across 55 domains, webhook
signature verification, interactive message builders, and per-process
test stubs.
## Installation
Add `whatsapp_sdk` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:whatsapp_sdk, "~> 0.1.0"}
]
end
```
Requires **Elixir 1.19+** and **OTP 27+**.
## Configuration
```elixir
# config/runtime.exs
config :whatsapp_sdk,
access_token: System.fetch_env!("WHATSAPP_ACCESS_TOKEN"),
phone_number_id: System.fetch_env!("WHATSAPP_PHONE_NUMBER_ID")
```
Optional global defaults (all have sensible defaults if omitted):
```elixir
config :whatsapp_sdk,
access_token: "your_token",
phone_number_id: "12345",
waba_id: "67890", # WhatsApp Business Account ID
api_version: "v23.0", # pin API version
max_retries: 3, # default: 0
base_url: "https://graph.facebook.com"
```
## Quick Start
```elixir
client = WhatsApp.client()
# Send a text message
{:ok, result} = WhatsApp.Messages.MessagesService.send_message(client, %{
"messaging_product" => "whatsapp",
"to" => "15551234567",
"type" => "text",
"text" => %{"body" => "Hello from Elixir!"}
})
# Retrieve media metadata
{:ok, media} = WhatsApp.Media.RootService.get_media_url(client, "media_id_123")
# Delete a message template
{:ok, _} = WhatsApp.Templates.MessageTemplatesService.delete_template_by_name(client,
name: "my_template"
)
```
Responses are automatically deserialized into typed structs:
```elixir
result.__struct__ #=> WhatsApp.Resources.SendMessage
media.__struct__ #=> WhatsApp.Resources.GetMediaUrl
```
Override config per-client for multi-account scenarios:
```elixir
client = WhatsApp.client("other_token",
phone_number_id: "99999",
max_retries: 5
)
```
### Handle errors
```elixir
case WhatsApp.Messages.MessagesService.send_message(client, params) do
{:ok, result} ->
result
{:error, %WhatsApp.Error{code: 190}} ->
Logger.error("Invalid access token")
{:error, %WhatsApp.Error{status: 429, retry_after: seconds}} ->
Logger.warning("Rate limited, retry after #{seconds}s")
{:error, %WhatsApp.Error{} = err} ->
Logger.error("WhatsApp error #{err.code}: #{err.message}")
end
```
### Receive webhooks (Phoenix)
```elixir
# router.ex
forward "/webhook/whatsapp", WhatsApp.WebhookPlug,
app_secret: "your_app_secret",
verify_token: "your_verify_token",
handler: MyApp.WhatsAppHandler
# handler.ex
defmodule MyApp.WhatsAppHandler do
@behaviour WhatsApp.WebhookPlug.Handler
@impl true
def handle_event(%{"messages" => messages}) do
Enum.each(messages, &process_message/1)
:ok
end
def handle_event(_event), do: :ok
end
```
### Build interactive messages
```elixir
alias WhatsApp.Interactive
payload =
Interactive.buttons("Would you like to proceed?")
|> Interactive.button("yes", "Yes")
|> Interactive.button("no", "No")
|> Interactive.build()
```
### Write tests
```elixir
# test/test_helper.exs
WhatsApp.Test.start()
ExUnit.start()
# test/my_app/notifier_test.exs
defmodule MyApp.NotifierTest do
use ExUnit.Case, async: true
setup do
WhatsApp.Test.stub(fn _request ->
%{status: 200, body: ~s({"messages":[{"id":"wamid.123"}]}), headers: []}
end)
:ok
end
test "sends message" do
assert {:ok, _} = MyApp.Notifier.send("Hello!")
end
end
```
## Features
### SDK
- **Full API coverage** — every endpoint from Meta's v23.0 OpenAPI spec, with
dedicated service modules organized by domain
- **Typed resources** — API responses are deserialized into 352 typed Elixir
structs with `@type t` definitions via an object type registry
- **Auto-paging pagination** — cursor-based `WhatsApp.Page` with lazy
`Stream.unfold` auto-paging for list endpoints
- **Webhook verification** — HMAC-SHA256 signature verification with optional
Phoenix Plug and Handler behaviour
- **Interactive messages** — pipeline builders for buttons, lists, CTA URLs,
flows, location requests, and product messages
- **Documentation** — `@moduledoc`, `@doc`, and `@spec` on all generated
modules, sourced from the OpenAPI spec
### Client
- **Finch HTTP client** — HTTP/2-capable with connection pooling via NimblePool,
zero JSON deps (uses Elixir 1.19 native `JSON`)
- **Automatic retries** — exponential backoff with jitter, `Retry-After`
parsing, Meta `is_transient` awareness
- **Response deserialization** — JSON to typed structs via object type registry
- **Telemetry** — `:start`, `:stop`, `:exception`, `:retry` events for every
request
- **Per-client configuration** — explicit struct with no global mutable state,
safe for concurrent use with multiple tokens or accounts
- **Test stubs** — per-process HTTP stubs via NimbleOwnership for
`async: true` tests
## Guides
- [Getting Started](guides/getting-started.md) — installation, configuration, first API call, error handling
- [Webhooks](guides/webhooks.md) — signature verification, WebhookPlug setup
- [Interactive Messages](guides/interactive-messages.md) — buttons, lists, CTA URLs, flows, products
- [Testing](guides/testing.md) — process-scoped HTTP stubs with `async: true` support
## Telemetry Events
| Event | Measurements | Metadata |
|-------|-------------|----------|
| `[:whatsapp, :request, :start]` | `system_time` | `method`, `path` |
| `[:whatsapp, :request, :stop]` | `duration` | `method`, `path`, `status` |
| `[:whatsapp, :request, :exception]` | `duration` | `method`, `path`, `kind`, `reason` |
| `[:whatsapp, :request, :retry]` | `system_time` | `method`, `path`, `attempt`, `reason`, `wait_ms` |
## Development
```bash
# Sync the latest spec
./scripts/sync_openapi.sh
# Regenerate modules
mix whatsapp.generate --clean
# Verify
mix compile --warnings-as-errors
mix test
mix credo --strict
mix dialyzer
```
### Code Generation
The SDK is auto-generated from Meta's WhatsApp Business Platform OpenAPI spec
via `mix whatsapp.generate`. The generator produces:
- **79 service modules** with typed `@spec` annotations
- **352 resource structs** with `@type t` definitions
- **1 object type registry** mapping schema names to modules (360 entries)
Organized across **55 API domains** covering **113 operations**.
### Parity Testing
Spec parity is a hard invariant. The test suite includes dedicated parity
assertions comparing the generated module set against the OpenAPI spec —
domain count, operation count, service module count, and resource module count.
## References
- [WhatsApp Cloud API Documentation](https://developers.facebook.com/docs/whatsapp/cloud-api)
- [Meta Business SDK — Getting Started](https://developers.facebook.com/docs/business-sdk/getting-started)
- [Meta OpenAPI Spec Repository](https://github.com/facebook/openapi)
## License
MIT — see [LICENSE](LICENSE) for details.