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

# Elixir SDK

> Install the Elixir SDK, configure for Phoenix, send messages, handle errors, and use WebhookPlug or WebhookHandler.

The Elixir SDK provides a typed client for the Surge API plus a Phoenix-friendly `WebhookPlug` and `WebhookHandler` behaviour for handling inbound events. It's published as `surge_api` on Hex and supports Elixir 1.14 and newer.

This page covers installing the package, configuring the client for Phoenix, sending your first request, handling errors, and wiring webhook routes with either the plug or the behaviour.

## Install and authenticate

Add to your `mix.exs`:

```elixir mix.exs theme={null}
defp deps do
  [
    {:surge_api, "~> 0.2.0"}
  ]
end
```

```bash theme={null}
mix deps.get
```

Set your API key in config:

```elixir config/runtime.exs theme={null}
config :surge_api, api_key: System.get_env("SURGE_API_KEY")
```

Or create a client directly:

```elixir theme={null}
client = Surge.Client.new("sk_live_your_key_here")
```

## Your first request

Using the default client (reads from application config):

```elixir theme={null}
{:ok, message} = Surge.Messages.create(
  "acct_01jrzhe8d9enptypyx360pcmxj",
  %{to: "+18015551234", body: "Your appointment is confirmed for Friday at 2pm."}
)

IO.puts(message.id)     # "msg_01j..."
IO.puts(message.status) # "queued"
```

Or with an explicit client:

```elixir theme={null}
client = Surge.Client.new("sk_live_your_key_here")

{:ok, message} = Surge.Messages.create(
  client,
  "acct_01jrzhe8d9enptypyx360pcmxj",
  %{to: "+18015551234", body: "Your appointment is confirmed for Friday at 2pm."}
)
```

## Retrieving a message

```elixir theme={null}
{:ok, message} = Surge.Messages.get("msg_01kqbhwra9egg8sdcsp9veg391")
IO.inspect(message.body)
```

## Error handling

The SDK returns tagged tuples:

```elixir theme={null}
case Surge.Messages.create("acct_01j...", %{to: "+18015551234", body: "Hello"}) do
  {:ok, message} ->
    IO.inspect(message.id)

  {:error, %Surge.Error{type: "opted_out", message: msg}} ->
    Logger.warning("Contact opted out: #{msg}")

  {:error, %Surge.Error{type: type, message: msg}} ->
    Logger.error("Surge error #{type}: #{msg}")
end
```

## Webhook handling

### WebhookPlug for Phoenix

The SDK includes `Surge.WebhookPlug` that handles signature verification and event parsing. Add it to your `endpoint.ex` **before** `Plug.Parsers`:

```elixir lib/my_app_web/endpoint.ex theme={null}
plug Surge.WebhookPlug,
  at: "/webhook/surge",
  handler: MyAppWeb.SurgeHandler,
  secret: System.get_env("SURGE_WEBHOOK_SECRET")
```

For runtime configuration, pass a tuple or function as the secret:

```elixir theme={null}
plug Surge.WebhookPlug,
  at: "/webhook/surge",
  handler: MyAppWeb.SurgeHandler,
  secret: {Application, :get_env, [:myapp, :surge_webhook_secret]}
```

### WebhookHandler behaviour

Implement the `Surge.WebhookHandler` behaviour to process events. The callback receives a `Surge.Events.Event` struct with an atom `type` and a `data` field:

```elixir theme={null}
defmodule MyAppWeb.SurgeHandler do
  @behaviour Surge.WebhookHandler

  @impl true
  def handle_event(%Surge.Events.Event{type: :message_received} = event) do
    IO.inspect(event.data.body, label: "Received")
    :ok
  end

  @impl true
  def handle_event(%Surge.Events.Event{type: :contact_opted_out} = event) do
    MyApp.handle_opt_out(event.data)
    :ok
  end

  @impl true
  def handle_event(_event), do: :ok
end
```

The callback should return `:ok` or `{:ok, term}` for success, or `:error` or `{:error, reason}` to signal a failure (which returns HTTP 400 to Surge).

### Manual signature verification

<Note>
  The Elixir SDK currently consumes the older `surge-signature` header (format `t=<ts>,v1=<hex>`) with a different signed-payload composition than the Standard Webhooks spec used by Python / TypeScript / Ruby. **Migrating to Standard Webhooks (`webhook-signature`) this week** — this Note will be removed once the migration ships. See [Signature validation](../api-reference/webhooks/signature-validation) for both formats.
</Note>

If you're not using `Surge.WebhookPlug`, verify signatures with `Surge.Webhook.construct_event/3`:

```elixir theme={null}
case Surge.Webhook.construct_event(raw_body, signature_header, webhook_secret) do
  {:ok, %Surge.Events.Event{} = event} ->
    handle_event(event)

  {:error, reason} ->
    Logger.warning("Invalid webhook signature: #{inspect(reason)}")
end
```

The `signature_header` is the value of the `surge-signature` HTTP header, formatted as `t=timestamp,v1=hex_signature`.
