Hex.pm Docs

A complete, production-grade Elixir client for the Mercury Banking API — accounts, transactions, recipients, invoices, payments, treasury, webhooks, and more.

Ported from and kept in parity with the reference Go SDK, adapted to idiomatic Elixir: {:ok, _} | {:error, _} tuples with ! raising variants, typed exception structs, lazy auto-paginating Streams, and a Req-based transport with automatic retry.

Installation

def deps do
  [
    {:mercury, "~> 1.0"}
  ]
end

Quick start

client = Mercury.Client.new("secret-token:mercury_production_...")

{:ok, account} = Mercury.Accounts.get(client, "account-id")

{:ok, %Mercury.Page{items: accounts}} = Mercury.Accounts.list(client, limit: 25)

# Lazy, auto-paginating stream across every page:
client
|> Mercury.Accounts.stream()
|> Enum.each(&IO.inspect/1)

# Every function has a `!` variant that raises instead of returning a tuple:
account = Mercury.Accounts.get!(client, "account-id")

Configuration

Mercury.Client.new("secret-token:mercury_production_...",
  base_url: "https://api.mercury.com/api/v1",   # override for a sandbox
  timeout: 30_000,                               # per-request timeout, ms
  retry: %Mercury.Retry{max_retries: 3},         # see Mercury.Retry
  on_request: fn method, url, request_id ->
    Logger.debug("mercury request #{method} #{url} #{request_id}")
  end,
  on_response: fn method, url, request_id, status, duration_ms ->
    Logger.debug("mercury response #{method} #{url} #{status} #{duration_ms}ms")
  end,
  req_options: [plug: {Req.Test, MyStub}]        # escape hatch for tests, proxies, etc.
)

Errors

Every error is one of the typed exceptions in Mercury.*Error: Mercury.AuthenticationError, Mercury.NotFoundError, Mercury.ValidationError, Mercury.ConflictError, Mercury.RateLimitError, Mercury.ServerError, Mercury.NetworkError, Mercury.TimeoutError, and the catch-all Mercury.APIError.

case Mercury.Accounts.get(client, id) do
  {:ok, account} ->
    account

  {:error, error} ->
    cond do
      Mercury.Error.not_found_error?(error) -> :not_found
      Mercury.Error.rate_limit_error?(error) -> :try_again_later
      true -> reraise error, __STACKTRACE__
    end
end

Rate limit (429) and server (5xx) errors are automatically retried with exponential backoff and jitter — see Mercury.Retry.

Resources

ModuleCovers
Mercury.Accountsaccounts, cards, statements, sending money
Mercury.Transactionscross-account transaction lookup, updates, attachments
Mercury.Recipientspayment recipients, tax form attachments
Mercury.Invoicesaccounts-receivable invoices
Mercury.Paymentsinternal transfers, send-money approval requests
Mercury.Categoriesexpense categories
Mercury.Customersaccounts-receivable customers
Mercury.Treasurytreasury accounts, transactions, statements
Mercury.Webhookswebhook endpoints + inbound signature verification
Mercury.Eventsaudit log
Mercury.Organizationorg info, partner onboarding
Mercury.Usersplatform users
Mercury.SafeRequestsSAFE (equity) requests
Mercury.Creditcredit accounts
Mercury.Attachmentsfile attachment metadata
Mercury.OAuth2partner OAuth2 authorization flow

Webhooks

defmodule MyAppWeb.MercuryWebhookController do
  use MyAppWeb, :controller

  def create(conn, _params) do
    raw_body = conn.assigns.raw_body # captured by a raw-body-preserving plug
    signature = conn |> Plug.Conn.get_req_header("mercury-signature") |> List.first()

    if Mercury.Webhooks.verify_signature(raw_body, signature, signing_secret()) do
      # handle the event
      send_resp(conn, 200, "")
    else
      send_resp(conn, 401, "invalid signature")
    end
  end
end

Confirm the exact header name and signing scheme against the current Mercury webhooks documentation — see the Mercury.Webhooks.verify_signature/3 docs for details.

Naming conventions

  • Response structs use snake_case fields (account.legal_business_name), converted from the API's camelCase.
  • create/update functions accept snake_case keys in a map or keyword list and camelize them automatically before sending.
  • Enum-like values (payment methods, wire types, statuses) should be given as atoms or strings that already match Mercury's wire format exactly, e.g. :domesticWire, "businessChecking".
  • Deeply nested, provider-specific objects (routing info, merchant data, currency exchange info) are returned as plain maps rather than fully modeled structs — consult the Mercury API reference for their shape.
  • Mercury.Transaction groups its least-frequently-used nested fields (glAllocations, attachments, relatedTransactions, categoryData, merchant, currencyExchangeInfo, details) under a single transaction.extra map with their original camelCase keys, to keep the struct under BEAM's 32-field threshold.

Development

mix deps.get
mix test
mix credo --strict
mix dialyzer
mix docs

License

MIT — see LICENSE.