Packages

Comprehensive Elixir SDK for the WhatsApp Business Platform Cloud API with full endpoint coverage. 79 service modules, 352 typed resource structs, and 113 operations generated from Meta's official OpenAPI spec.

Current section

Files

Jump to
whatsapp_sdk README.md
Raw

README.md

# WhatsApp SDK for Elixir
[![Hex.pm](https://img.shields.io/hexpm/v/whatsapp_sdk.svg)](https://hex.pm/packages/whatsapp_sdk)
[![CI](https://github.com/jeffhuen/whatsapp_sdk/actions/workflows/ci.yml/badge.svg)](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.