hackney

An HTTP client for Erlang. Simple, reliable, fast.

Build StatusHex pm

Why hackney?

Quick Start

%% Start hackney
application:ensure_all_started(hackney).
%% Simple GET - the body is returned directly
{ok, 200, _Headers, Body} = hackney:get(<<"https://httpbin.org/get">>).
%% POST JSON
Headers = [{<<"content-type">>, <<"application/json">>}],
Payload = <<"{\"key\": \"value\"}">>,
{ok, 200, _, _} = hackney:post(<<"https://httpbin.org/post">>, Headers, Payload).

Installation

Rebar3

{deps, [hackney]}.

Mix

{:hackney, "~> 4.0"}

Documentation

GuideDescription
Getting StartedInstallation, first requests, basic patterns
HTTP GuideRequests, responses, streaming, async, pools
HTTP/2 GuideHTTP/2 protocol, ALPN, multiplexing, flow control
HTTP/3 GuideHTTP/3 over QUIC, opt-in configuration, Alt-Svc
WebSocket GuideConnect, send, receive, active mode
WebTransport GuideStreams, datagrams, multiplexing, server handlers
Design GuideArchitecture, pooling, load regulation internals
Migration GuideUpgrading from hackney 1.x
API ReferenceFull module documentation
ChangelogVersion history

Features

HTTP Methods

All standard HTTP methods as convenient functions:

hackney:get(URL).
hackney:post(URL, Headers, Body).
hackney:put(URL, Headers, Body).
hackney:delete(URL).
hackney:head(URL).
hackney:options(URL).
hackney:patch(URL, Headers, Body).

Connection Pooling

Connections are pooled by default. Configure pools for different use cases:

%% Use default pool
hackney:get(URL).
%% Named pool with custom settings
hackney_pool:start_pool(api_pool, [{max_connections, 100}]),
hackney:get(URL, [], <<>>, [{pool, api_pool}]).
%% No pooling for one-off requests
hackney:get(URL, [], <<>>, [{pool, false}]).

Streaming

Stream request bodies for uploads:

{ok, Ref} = hackney:post(URL, Headers, stream),
hackney:send_body(Ref, <<"chunk 1">>),
hackney:send_body(Ref, <<"chunk 2">>),
hackney:finish_send_body(Ref),
{ok, Status, _, Ref} = hackney:start_response(Ref),
{ok, Body} = hackney:body(Ref).

Sync responses return the full body directly. To receive a large response piece-by-piece, use the async API (see the Async Responses section below).

Async Responses

Receive response data as messages:

{ok, Ref} = hackney:get(URL, [], <<>>, [async]),
receive
{hackney_response, Ref, {status, 200, _}} -> ok
end,
receive
{hackney_response, Ref, {headers, Headers}} -> ok
end,
receive_body(Ref).
receive_body(Ref) ->
receive
{hackney_response, Ref, done} -> ok;
{hackney_response, Ref, Bin} -> receive_body(Ref)
end.

WebSocket

{ok, Conn} = hackney:ws_connect(<<"wss://echo.websocket.org">>),
ok = hackney:ws_send(Conn, {text, <<"hello">>}),
{ok, {text, <<"hello">>}} = hackney:ws_recv(Conn),
hackney:ws_close(Conn).

WebTransport

Same shape as WebSocket, over HTTP/3 (QUIC). Swap ws_ for wt_:

{ok, Conn} = hackney:wt_connect(<<"https://example.com/wt">>),
ok = hackney:wt_send(Conn, {binary, <<"hello">>}),
{ok, {binary, <<"hello">>}} = hackney:wt_recv(Conn),
hackney:wt_close(Conn).

HTTP/2

HTTP/2 is used automatically when the server supports it:

%% Automatic HTTP/2 via ALPN negotiation
{ok, 200, Headers, Body} = hackney:get(<<"https://nghttp2.org/">>).
%% Force HTTP/1.1 only
hackney:get(URL, [], <<>>, [{protocols, [http1]}]).
%% Force HTTP/2 only
hackney:get(URL, [], <<>>, [{protocols, [http2]}]).

HTTP/3 (Experimental)

HTTP/3 support is opt-in. Enable it per-request or globally:

%% Enable HTTP/3 for a single request
hackney:get(URL, [], <<>>, [{protocols, [http3, http2, http1]}]).
%% Enable HTTP/3 globally (application-wide)
application:set_env(hackney, default_protocols, [http3, http2, http1]).

Note: HTTP/3 uses QUIC (UDP transport). Some networks may block UDP traffic.

Multipart

Upload files and form data:

Multipart = {multipart, [
{<<"field">>, <<"value">>},
{file, <<"/path/to/file.txt">>},
{file, <<"/path/to/image.png">>, <<"image.png">>, [{<<"content-type">>, <<"image/png">>}]}
]},
hackney:post(URL, [], Multipart).

Proxy Support

%% HTTP proxy
hackney:get(URL, [], <<>>, [{proxy, <<"http://proxy:8080">>}]).
%% With authentication
hackney:get(URL, [], <<>>, [{proxy, <<"http://user:pass@proxy:8080">>}]).
%% Environment variables work automatically
%% HTTP_PROXY, HTTPS_PROXY, NO_PROXY

Redirects

%% Follow redirects automatically
hackney:get(URL, [], <<>>, [{follow_redirect, true}, {max_redirect, 5}]).

Timeouts

hackney:get(URL, [], <<>>, [
{connect_timeout, 5000}, %% Connection timeout
{recv_timeout, 30000} %% Response timeout
]).

Automatic Decompression

%% Automatically decompress gzip/deflate responses
hackney:get(URL, [], <<>>, [{auto_decompress, true}]).

SSL Options

%% Custom CA certificate
hackney:get(URL, [], <<>>, [
{ssl_options, [{cacertfile, "/path/to/ca.pem"}]}
]).
%% Skip verification (development only)
hackney:get(URL, [], <<>>, [insecure]).

Modules

ModulePurpose
hackneyMain API - requests, connections, WebSocket
hackney_poolConnection pool management
hackney_urlURL parsing and encoding
hackney_headersHeader manipulation
hackney_multipartMultipart encoding
hackney_cookieCookie parsing
hackney_httpHTTP protocol parser

Requirements

Erlang/OTP 27+

Contributing

See CONTRIBUTING.md for guidelines on pull requests and development setup.

Issues and pull requests welcome at https://github.com/benoitc/hackney

Support

hackney is critical infrastructure for many Erlang and Elixir applications. If your company relies on it, consider sponsoring its maintenance:

Sponsor

Sponsorship ensures continued development, timely security patches, and compatibility with OTP releases.

Corporate sponsors: If hackney is part of your infrastructure, reach out for sponsored support options.

Sponsors

Enki Multimedia

License

Apache 2.0 - See LICENSE and NOTICE

Copyright (c) 2012-2026 Benoit Chesneau