Elixir client for the Safaricom Daraja API.
All API calls take a Daraja.Client as their first argument. Build a client
once and reuse it; per-request options on Daraja.Client.new/1 override
values read from the application environment, which is handy for multi-tenant
callers.
client = Daraja.Client.new()M-Pesa Express (STK Push)
Initiate a payment prompt on a customer's phone:
Daraja.Express.request(client, %{
amount: 100,
phone_number: "254712345678",
account_reference: "Order-001"
})Customer to Business (C2B)
Register callback URLs for payment notifications:
Daraja.C2B.register_url(client, %{
short_code: "600984",
response_type: "Completed",
confirmation_url: "https://example.com/c2b/confirmation",
validation_url: "https://example.com/c2b/validation"
})Simulate a payment in the sandbox environment:
Daraja.C2B.simulate(client, %{
short_code: "600984",
command_id: "CustomerPayBillOnline",
amount: 100,
msisdn: "254708374149",
bill_ref_number: "INV-001"
})Business to Customer (B2C)
Pass the initiator password and certificate as a tuple — PaymentRequest.new/1
encrypts it internally (the tuple form is sugar over calling
Daraja.SecurityCredential.encrypt/2 yourself):
Daraja.B2C.payment(client, %{
originator_conversation_id: "my-unique-id-001",
initiator_name: "testapi",
security_credential: {"your-initiator-password", File.read!("sandbox-cert.cer")},
command_id: "BusinessPayment",
amount: 10,
party_a: "600997",
party_b: "254705912645",
remarks: "Payout",
queue_timeout_url: "https://example.com/b2c/timeout",
result_url: "https://example.com/b2c/result",
occasion: "Promo"
})For production, pre-encrypt with Daraja.SecurityCredential.encrypt/2 at
deploy time and store only the resulting Base64 string so plaintext passwords
never live in application state:
{:ok, security_credential} =
Daraja.SecurityCredential.encrypt(
"your-initiator-password",
File.read!("cert.cer")
)
Daraja.B2C.payment(client, %{security_credential: security_credential, ...})Business to Business (B2B)
Same credential options as B2C — tuple for convenience, pre-encrypted string for production:
Daraja.B2B.request(client, %{
initiator: "testapi",
security_credential: {"your-initiator-password", File.read!("sandbox-cert.cer")},
command_id: "BusinessPayBill",
sender_identifier_type: 4,
receiver_identifier_type: 4,
amount: 10_500,
party_a: "600992",
party_b: "600000",
account_reference: "INV-001",
remarks: "B2B Payment",
queue_timeout_url: "https://example.com/b2b/timeout",
result_url: "https://example.com/b2b/result"
})Configuration
Configure default credentials in your application config:
config :daraja,
consumer_key: "...",
consumer_secret: "...",
business_short_code: "174379",
passkey: "bfb279...",
callback_url: "https://example.com/callback",
environment: :sandbox,
b2b_initiator: "testapi",
b2b_security_credential: "base64-credential",
b2b_queue_timeout_url: "https://example.com/b2b/timeout",
b2b_result_url: "https://example.com/b2b/result",
b2c_initiator_name: "testapi",
b2c_security_credential: "base64-credential",
b2c_queue_timeout_url: "https://example.com/b2c/timeout",
b2c_result_url: "https://example.com/b2c/result"The b2b_* and b2c_* keys are optional defaults for Daraja.B2B.request/2
and Daraja.B2C.payment/2. Per-call params always take precedence over env
values, which is handy for multi-tenant callers that need to override
defaults per request.
Per-call overrides for multi-tenant callers:
client =
Daraja.Client.new(
consumer_key: merchant.consumer_key,
consumer_secret: merchant.consumer_secret
)Callback security
Daraja does not sign inbound webhooks. Use Daraja.Callback.Security for IP
allowlisting and shared-secret checks, Daraja.Callback.Guard for idempotency,
and parse/1 on the product callback modules before fulfilling orders.
Default callback IP allowlists are community-documented Safaricom ranges—not fetched from the live Daraja API. Override them for production:
config :daraja,
callback_cidrs: ["196.201.212.0/24", "196.201.213.0/24", "196.201.214.0/24"],
callback_hosts: ["196.201.214.200", "196.201.212.127"]Token Caching
Production deployments must start both Finch and Daraja.Supervisor. Without
the supervisor, every API call performs a separate OAuth round-trip (logged once
as a warning when :warn_uncached_token is enabled, which is the default outside
:test).
Add both to your application's supervision tree:
children = [
{Finch, name: Daraja.Finch},
{Daraja.Supervisor, []}
]In umbrella apps, start one supervisor per credential set with distinct names and point the library at the right cache via config:
# supervision tree
{Daraja.Supervisor, name: :billing_sup, cache_name: :billing_cache}
# config/config.exs (or the umbrella child's config)
config :daraja, token_cache: :billing_cacheThe :token_cache config value must be an atom. Omit it to use the default
Daraja.TokenCache. See Daraja.Auth.get_token/1 for details.
HTTP timeouts and retries
The default Finch adapter uses a 10 second receive timeout. Override in config:
config :daraja, :http_receive_timeout, 10_000OAuth token fetches can opt into retry with exponential backoff (disabled by default):
config :daraja, :http_retry,
enabled: true,
max_attempts: 3,
base_ms: 100,
max_ms: 2_000Payment POST calls are never retried automatically.
Custom HTTP Client
Implement Daraja.HTTPClient and configure it:
config :daraja, :http_client, MyApp.CustomHTTPClientCustom adapters must verify TLS peers, avoid logging credentials or request
bodies, use bounded timeouts, and not follow redirects to unintended hosts.
See Daraja.HTTPClient.Compliance for a review checklist.