Four steps with Req and Jason — HMAC verification uses Erlang's built-in :crypto. Works with Phoenix, Plug, or any OTP application.
Add Req for HTTP and Jason for JSON. HMAC verification uses Erlang's built-in :crypto module — no extra dependency needed.
Exchange client credentials for a bearer token. Cache the token in an Agent or GenServer and refresh before the 3600-second expiry.
Upload the PDF as a multipart request alongside signer details and field coordinates. The response includes the envelope ID you use to send.
When signing completes, GetSigned POSTs to your webhook URL. Use Erlang's :crypto to verify the HMAC-SHA256 signature before processing the event.
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.
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.
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.
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.
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
Free tier — 25 envelopes per month. Full API access from day one.
Get free API keys →