Current section
Files
Jump to
Current section
Files
README.md
# Lithic
Production-grade Elixir client for the [Lithic API](https://docs.lithic.com) — card issuing, fintech infrastructure, and embedded finance.
[](https://hex.pm/packages/lithic)
[](https://hexdocs.pm/lithic)
## Features
- **Complete API coverage** — all 150+ endpoints across Cards, Payments, ACH, Tokenization, 3DS, Auth Rules, Events, Credit, Transaction Monitoring, and more
- **Cursor-based pagination** — `Page.stream/1` for lazy enumeration across all list endpoints
- **Automatic retries** — exponential backoff with jitter on 5xx and network errors
- **Idempotency keys** — auto-generated on every mutating request
- **Webhook verification** — HMAC-SHA256 for all webhook types (Events, ASA, Tokenization Decisioning, 3DS Decisioning)
- **Telemetry** — `[:lithic, :request_start/stop/exception]` events with duration
- **Zero dependencies** — only `req`, `jason`, and `telemetry`
- **Dialyzer + Credo** — full typespec coverage and strict linting
## Installation
```elixir
def deps do
[
{:lithic, "~> 1.0"}
]
end
```
## Configuration
```elixir
# config/config.exs
config :lithic,
api_key: System.get_env("LITHIC_API_KEY"),
environment: :production # :production | :sandbox
```
Or pass a client per-request:
```elixir
client = Lithic.Client.new(api_key: "my_key", environment: :sandbox)
{:ok, card} = Lithic.Cards.create(%{type: "VIRTUAL"}, client: client)
```
## Quick Start
```elixir
# Create a virtual card
{:ok, card} = Lithic.Cards.create(%{type: "VIRTUAL"})
# Pause it
{:ok, _} = Lithic.Cards.update(card["token"], %{state: "PAUSED"})
# List cards (paginated)
{:ok, page} = Lithic.Cards.list(page_size: 25, state: "OPEN")
page.data #=> [%{"token" => ..., "type" => "VIRTUAL", ...}, ...]
page.has_more #=> true
# Lazily stream all cards
Lithic.Cards.stream()
|> Stream.filter(&(&1["state"] == "OPEN"))
|> Enum.take(100)
# Collect all (use carefully on large datasets)
{:ok, all} = Lithic.Cards.list() |> then(fn {:ok, p} -> Lithic.Page.collect_all(p) end)
```
## Payments (ACH)
```elixir
# Link an external bank account via micro-deposit
{:ok, eba} = Lithic.ExternalBankAccounts.create(%{
routing_number: "021000021",
account_number: "1234567890",
account_type: "CHECKING",
owner: "Jane Doe",
owner_type: "INDIVIDUAL",
verification_method: "MICRO_DEPOSIT"
})
# After deposits arrive (1-3 business days):
{:ok, _} = Lithic.ExternalBankAccounts.verify(eba["token"], %{micro_deposits: [12, 34]})
# Send a payment
{:ok, payment} = Lithic.Payments.create(%{
financial_account_token: "fa_token",
external_bank_account_token: eba["token"],
amount: 5000, # $50.00
direction: "DEBIT",
method: "ACH_NEXT_DAY",
method_attributes: %{sec_code: "PPD"},
type: "PAYMENT"
})
```
## Sandbox Simulation
```elixir
# Full transaction lifecycle
{:ok, txn} = Lithic.Transactions.simulate_authorization(%{
card_token: "card_token",
amount: 1000,
descriptor: "STARBUCKS",
mcc: "5812"
})
{:ok, _} = Lithic.Transactions.simulate_clearing(txn["token"])
# or
{:ok, _} = Lithic.Transactions.simulate_void(txn["token"])
```
## Auth Rules (V2)
```elixir
# Create a velocity limit rule
{:ok, rule} = Lithic.AuthRules.create(%{
name: "Daily spend limit",
parameters: %{scope: "CARD", limits: [%{limit: 100_00, period: "DAY"}]}
})
# Draft → backtest → promote
{:ok, _} = Lithic.AuthRules.draft(rule["token"], %{parameters: updated_params})
{:ok, bt} = Lithic.AuthRules.request_backtest(rule["token"], %{
start: "2024-01-01T00:00:00Z",
end: "2024-03-01T00:00:00Z"
})
{:ok, _} = Lithic.AuthRules.promote(rule["token"])
```
## Webhook Verification
```elixir
# In your Phoenix controller:
def handle(conn, _params) do
raw_body = conn.assigns.raw_body
signature = get_req_header(conn, "webhook-signature") |> List.first()
timestamp = get_req_header(conn, "webhook-timestamp") |> List.first()
{:ok, sub} = Lithic.Events.get_subscription_secret("sub_token")
case Lithic.Webhook.verify(raw_body, signature, timestamp, secret: sub["secret"]) do
{:ok, payload} ->
handle_event(payload["type"], payload)
send_resp(conn, 200, "ok")
{:error, reason} ->
send_resp(conn, 400, reason)
end
end
```
## Telemetry
```elixir
# Attach the default structured logger
Lithic.Telemetry.attach_default_logger(level: :info)
# Or write your own handler
:telemetry.attach(
"my-handler",
[:lithic, :request_stop],
fn _event, %{duration: d}, %{method: m, path: p}, _cfg ->
ms = System.convert_time_unit(d, :native, :millisecond)
Logger.info("[lithic] #{m} #{p} #{ms}ms")
end,
nil
)
```
## Resource Modules
| Module | Description |
|--------|-------------|
| Lithic.Cards | Card lifecycle, balances, provisioning |
| Lithic.Accounts | Account management |
| Lithic.AccountHolders | KYC/KYB |
| Lithic.AuthRules | V2 rules engine with backtesting |
| Lithic.AuthStreamAccess | Real-time ASA webhook |
| Lithic.Transactions | Card transactions + sandbox simulation |
| Lithic.Payments | ACH payments |
| Lithic.BookTransfers | Internal transfers |
| Lithic.ExternalBankAccounts | ACH counterparty accounts |
| Lithic.ExternalPayments | External payment lifecycle |
| Lithic.FinancialAccounts | Ledger, balances, credit config |
| Lithic.Balances | Balance queries |
| Lithic.Holds | Financial holds |
| Lithic.ManagementOperations | Manual ledger adjustments |
| Lithic.CardBulkOrders | Bulk physical card orders |
| Lithic.ThreeDS | 3DS auth and decisioning |
| Lithic.Tokenization | Digital wallet tokenization |
| Lithic.Events | Webhooks and event subscriptions |
| Lithic.Chargebacks | Chargeback/dispute (legacy) |
| Lithic.Disputes | Disputes V2 |
| Lithic.FraudReports | Fraud reporting |
| Lithic.Credit | Credit products, statements, loan tapes |
| Lithic.Settlement | Settlement summaries |
| Lithic.Network | Network programs and totals |
| Lithic.FundingEvents | Program-level funding |
| Lithic.TransactionMonitoring | Cases and queues |
## License
MIT