Developer How-To · Elixir

Add e-signatures
to an Elixir app

Four steps with Req and Jason — HMAC verification uses Erlang's built-in :crypto. Works with Phoenix, Plug, or any OTP application.

1

Add dependencies

Add Req for HTTP and Jason for JSON. HMAC verification uses Erlang's built-in :crypto module — no extra dependency needed.

elixir
# mix.exs
defp deps do
  [
    {:req,   "~> 0.5"},
    {:jason, "~> 1.4"}
  ]
end

# mix deps.get

# Note: HMAC-SHA256 uses Erlang's :crypto — no extra package.
2

Authenticate with OAuth2

Exchange client credentials for a bearer token. Cache the token in an Agent or GenServer and refresh before the 3600-second expiry.

elixir
defmodule GetSigned.Auth do
  def get_token(client_id, client_secret) do
    {:ok, response} =
      Req.post("https://api.getsigned.app/oauth/token",
        form: [
          grant_type:    "client_credentials",
          client_id:     client_id,
          client_secret: client_secret
        ]
      )

    response.body["access_token"]
  end
end
3

Create an envelope

Upload the PDF as a multipart request alongside signer details and field coordinates. The response includes the envelope ID you use to send.

elixir
defmodule GetSigned.Envelopes do
  def create(token, pdf_path, signer_name, signer_email) do
    pdf_content = File.read!(pdf_path)

    signers = Jason.encode!([%{name: signer_name, email: signer_email}])
    fields  = Jason.encode!([%{type: "signature", page: 1, x: 300, y: 580, w: 200, h: 60}])

    {:ok, response} =
      Req.post("https://api.getsigned.app/v1/envelopes",
        headers: [authorization: "Bearer #{token}"],
        multipart: [
          {:signers, signers},
          {:fields,  fields},
          {:document, pdf_content,
            content_type: "application/pdf",
            filename:     "document.pdf"}
        ]
      )

    response.body["id"]
  end

  def send_envelope(token, envelope_id) do
    {:ok, _} =
      Req.post("https://api.getsigned.app/v1/envelopes/#{envelope_id}/send",
        headers: [authorization: "Bearer #{token}"]
      )
    :ok
  end
end
4

Verify HMAC webhooks

When signing completes, GetSigned POSTs to your webhook URL. Use Erlang's :crypto to verify the HMAC-SHA256 signature before processing the event.

elixir
defmodule GetSigned.Webhook do
  # In your Phoenix controller — read raw body BEFORE Jason parses it.
  # Add plug :read_raw_body or use Plug.Parsers raw_body option.

  def verify_hmac(raw_body, sig_header, secret) do
    computed =
      :crypto.mac(:hmac, :sha256, secret, raw_body)
      |> Base.encode16(case: :lower)

    # Constant-time comparison — avoid timing attacks
    Plug.Crypto.secure_compare(computed, sig_header)
  end
end

# Phoenix router — capture raw body for HMAC verification:
# pipeline :api_raw do
#   plug :accepts, ["json"]
#   plug GetSigned.RawBodyPlug
# end

# envelope.completed → download the sealed PDF:
# Req.get(
#   "https://api.getsigned.app/v1/envelopes/#{id}/document",
#   headers: [authorization: "Bearer #{token}"]
# )

Frequently asked questions

Which HTTP client should I use for GetSigned in Elixir?

Req is the recommended modern choice — it has a clean functional API, handles multipart uploads natively, and works well with Elixir's async patterns. HTTPoison (based on hackney) is the older standard and also works. Finch is a lower-level option for high-performance scenarios. The code in this guide uses Req, which is the idiomatic choice for new Elixir projects as of 2024.

How do I capture the raw request body for HMAC verification in Phoenix?

Phoenix's JSON parser consumes the body stream, so you need to capture the raw bytes before parsing. Add a custom Plug that reads and caches the raw body before Plug.Parsers processes it. Store it in conn.assigns[:raw_body] and read it in your webhook controller. Never verify the signature against a re-serialized JSON body — whitespace differences will cause verification to fail.

How do I cache the OAuth2 token in an OTP application?

Use a GenServer or Agent to hold the token and its expiry time. On each API call, check if the cached token has more than 60 seconds remaining — if yes, use it; if not, fetch a new token and cache it. A supervised GenServer that refreshes proactively (e.g., every 55 minutes) is the production-grade pattern. This avoids per-request token fetches under load.

Does the Req multipart API support binary file content?

Yes. Pass the file content as a binary value in the multipart list with content_type and filename options. Req handles the multipart boundary generation, content-disposition headers, and content-type headers automatically. If you need to upload from a file path instead of in-memory bytes, use {:file, path} as the value — Req streams the file content without loading it fully into memory.

Is there an Elixir SDK for GetSigned?

No dedicated Hex package — the integration uses Req and Jason directly. This means no SDK version to track, no transitive dependency conflicts, and full control over the HTTP behavior. The complete integration shown here is under 60 lines of idiomatic Elixir.

Related: Ruby guide · Python guide · Rust guide · Webhook guide

Send your first envelope today

Free tier — 25 envelopes per month. Full API access from day one.

Get free API keys →