The official Dwolla command-line interface — explore the API, seed sandbox data, and automate workflows from your terminal.
Beta. The Dwolla CLI is in public beta. Commands and flags are stable but may still change ahead of
v1. Please share feedback.
Install via script:
# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/dwolla/dwolla-cli/v0.1.0-beta.1/scripts/install.sh | bash
# Windows (PowerShell)
irm https://raw.githubusercontent.com/dwolla/dwolla-cli/v0.1.0-beta.1/scripts/install.ps1 | iexEach URL points at a published release tag. The script downloads the latest
release for your platform and verifies its SHA-256 against the release
checksums.txt, aborting on any mismatch.
With Go installed:
go install github.com/dwolla/dwolla-cli/cmd/dwolla@latestManual: download the archive for your platform from
GitHub Releases and extract the
dwolla binary onto your PATH.
On macOS, until the binary is code-signed you may need to clear the quarantine attribute after a manual download:
xattr -d com.apple.quarantine ./dwolla.
Build from source:
git clone https://github.com/Dwolla/dwolla-cli.git
cd dwolla-cli
go build -o dwolla ./cmd/dwolla
./dwolla --helpRequires Go 1.25+.
export DWOLLA_CLIENT_ID=<your-sandbox-client-id>
export DWOLLA_CLIENT_SECRET=<your-sandbox-client-secret>
export DWOLLA_TOKEN_URL=https://api-sandbox.dwolla.com/token
./dwolla auth whoami # confirm credentials and environment
./dwolla root get # fetch your account root
./dwolla workflows run send-money # run an end-to-end sandbox scenario
./dwolla trigger transfer_completed # fire a real sandbox webhook event
./dwolla listen --print # receive webhooks over a public tunnel (Ctrl+C to stop)The CLI defaults to the sandbox environment. Set
DWOLLA_ENV=productionto target production. Prefer the keychain? Run./dwolla auth logininstead of exporting the variables above.
Credentials are resolved in priority order: command flag → environment variable → OS keychain → config file (~/.config/dwolla/config.yaml).
# Environment variables (recommended for testing)
export DWOLLA_CLIENT_ID=...
export DWOLLA_CLIENT_SECRET=...
export DWOLLA_TOKEN_URL=https://api-sandbox.dwolla.com/token
# Or store in the OS keychain
./dwolla auth login
# Inspect what's currently configured and where it came from
./dwolla auth whoami
# Clear stored credentials
./dwolla auth logoutEvery Dwolla API resource maps to a command group. Common pattern:
./dwolla <resource> <action> [flags]| Command | Aliases | Description |
|---|---|---|
root |
Root account info | |
customers |
Create, list, update customers | |
funding-sources |
fs |
Manage bank accounts |
transfers |
Initiate and track transfers | |
mass-payments |
mp |
Bulk payment operations |
events |
Webhook event log | |
webhook-subscriptions |
ws |
Manage webhook endpoints |
webhooks |
Inspect and retry deliveries | |
sandbox-simulations |
ss |
Simulate bank transfer processing |
accounts |
Master account operations | |
documents |
Identity document uploads | |
labels |
Customer balance labels | |
beneficial-owners |
bo |
UBO management |
exchanges |
Open Banking exchange integrations | |
exchange-partners |
ep |
Available exchange partners |
exchange-sessions |
es |
Exchange session management |
business-classifications |
bc |
Business classification lookup |
tokens |
Application access tokens | |
kba |
Knowledge-based authentication |
# List customers
./dwolla customers list
# Create a customer (interactive prompt)
./dwolla customers create
# Get a specific transfer
./dwolla transfers get --transfer-id <id>
# List recent events
./dwolla events list --limit 10
# Simulate bank transfer processing (sandbox only)
./dwolla sandbox-simulations simulateWorkflows run multi-step sandbox scenarios with a single command, chaining resource IDs and HAL links between steps automatically. Emails are automatically timestamped so repeated runs never collide.
# List all built-in workflows (grouped by tier)
./dwolla workflows list
# See flags for a specific workflow
./dwolla workflows list --slug send-money
# Run with default flags
./dwolla workflows run send-money
# Pass workflow-specific flags
./dwolla workflows run send-money --customer-type business-verified --rail ach-same-day
./dwolla workflows run facilitate-payment --fee 2.50 --charge-to receiver
# Preview the step plan without making any API calls
./dwolla workflows run send-money --dry-run
# Patch a specific step parameter
./dwolla workflows run send-money --override receiver:firstName=Alice
# Run a custom workflow file
./dwolla workflows run ./my-workflow.json| Slug | Tier | Description |
|---|---|---|
send-money |
Payment | Onboard a customer and send funds to your account balance |
receive-money |
Payment | Receive funds from a customer's verified bank |
me-to-me |
Payment | Move funds between two of a customer's own bank accounts |
facilitate-payment |
Payment | Marketplace: payer → receiver with a platform facilitator fee |
onboard-receive-only |
Onboarding | Create a receive-only customer |
onboard-unverified |
Onboarding | Create an unverified customer, optionally with a bank (--with-bank=false to skip) |
onboard-personal-verified |
Onboarding | Create a personal-verified customer with a verified bank |
onboard-business-verified |
Onboarding | Create a business-verified customer (controller, beneficial owner, certification) |
seed-sandbox |
Setup | Populate a fresh sandbox with all four customer types and a sample transfer |
Workflow files are JSON with a steps array. Each step is one API call; later steps reference earlier results with ${stepName:field}.
{
"_meta": {
"template_version": 1,
"name": "my-workflow",
"description": "Create a customer and attach a bank."
},
"steps": [
{
"name": "customer",
"method": "POST",
"path": "/customers",
"headers": { "Idempotency-Key": "${.uuid}" },
"params": {
"firstName": "Ada",
"lastName": "Lovelace",
"email": "ada+${.timestamp}@example.com",
"type": "personal",
"address1": "123 Main St",
"city": "Des Moines",
"state": "IA",
"postalCode": "50309",
"dateOfBirth": "1990-01-01",
"ssn": "1234"
}
},
{
"name": "bank",
"method": "POST",
"path": "/customers/${customer:id}/funding-sources",
"headers": { "Idempotency-Key": "${.uuid}" },
"params": {
"routingNumber": "222222226",
"accountNumber": "100200300",
"bankAccountType": "checking",
"name": "Ada Checking"
}
}
],
"env": {
"CUSTOMER_ID": "${customer:id}",
"BANK_URL": "${bank:location}"
}
}Variable reference syntax:
| Form | Resolves to |
|---|---|
${stepName:id} |
Resource ID extracted from Location header or _links.self.href |
${stepName:location} |
Full Location header URL |
${stepName:dotted.path} |
JSONPath into the step's response body |
${stepName:dotted.path[@key=val].field} |
Inline array filter |
${.timestamp} |
Unix seconds at workflow start (use in emails to avoid duplicates) |
${.uuid} |
Fresh random UUID per step (use as idempotency key) |
${.env:VAR|default} |
Environment variable with optional fallback |
See examples/ for full working workflow files.
trigger fires a real Dwolla sandbox webhook event by creating the necessary resources and, where required, running simulation passes. Useful for testing webhook handlers without manual setup.
# List available events
./dwolla trigger --list
# Fire an event
./dwolla trigger customer_created
./dwolla trigger transfer_completed # creates resources + 2 simulation passes
./dwolla trigger transfer_failed # R01 bank → 2 simulation passes → failure event| Event | What it does |
|---|---|
customer_created |
Creates a receive-only customer |
customer_verified |
Creates a personal-verified customer (auto-passes KYC in sandbox) |
funding_source_added |
Creates a customer and attaches a bank account |
funding_source_verified |
Attaches a bank and completes micro-deposit verification |
transfer_created |
Creates a customer, bank, and initiates a transfer |
transfer_completed |
Initiates a transfer and simulates it to completion (2 passes) |
transfer_failed |
Initiates a transfer with an R01 bank and simulates it to failure (2 passes) |
Triggers are sandbox-only and refuse to run against production credentials.
listen opens a public tunnel, registers a Dwolla webhook subscription pointing at it, and either forwards incoming events to a local URL, prints them to stdout, or both. The subscription is automatically removed when you exit (Ctrl+C).
# Forward webhooks to your local app
dwolla listen --forward-to localhost:3000/webhooks
# Print payloads to stdout — no local app needed
dwolla listen --print
# Forward AND print (useful for debugging alongside your app)
dwolla listen --forward-to localhost:3000/webhooks --print
# Filter to specific event topics only
dwolla listen --forward-to localhost:3000/webhooks --events transfer_completed,customer_verified
# Force a specific tunnel provider
dwolla listen --forward-to localhost:3000/webhooks --tunnel localtunnellisten uses Cloudflare Quick Tunnels by default — no Cloudflare account or domain ownership required. The tunnel URL looks like https://random-name.trycloudflare.com.
Cloudflare requires the cloudflared binary to be in your PATH. If it is not found, the CLI will prompt you to download and install it automatically (~14 MB, from github.com/cloudflare/cloudflared). The installed binary goes to ~/.local/bin/cloudflared on macOS/Linux or %LOCALAPPDATA%\cloudflared\cloudflared.exe on Windows. You can also install it yourself beforehand:
# macOS
brew install cloudflared
# Linux
# See https://pkg.cloudflare.com/ for package installs, or download directly:
# https://github.com/cloudflare/cloudflared/releasesIf cloudflared is unavailable and you decline the download (or the command is running non-interactively), listen falls back to localtunnel.me — a pure Go fallback with no binary dependency. The tunnel URL looks like https://random-name.loca.lt.
Use --tunnel to force a specific provider:
| Value | Behavior |
|---|---|
| (default) | Cloudflare if available, prompt to download if not, fall back to localtunnel |
cloudflare |
Cloudflare only — error if cloudflared is not installed |
localtunnel |
localtunnel.me only — skips the download prompt |
- A random HMAC secret is generated for the session.
- A tunnel is opened and a webhook subscription is registered at the tunnel URL with that secret.
- Each incoming webhook is verified against the
X-Request-Signature-SHA-256header — requests with a missing or invalid signature are rejected with 401. - Matching events are forwarded to
--forward-to(if set) and/or printed to stdout (if--printis set). Filtered-out events are logged as skipped. - On exit, the subscription is deleted. If a previous session exited uncleanly, any orphaned subscriptions (from either provider) are cleaned up automatically on the next startup.
listen and trigger are designed to work together. Open two terminals:
Terminal A — start listening:
dwolla listen --printTerminal B — fire a real sandbox event:
dwolla trigger transfer_completedDwolla delivers the webhook to the tunnel URL, which the CLI verifies and prints to Terminal A. Press Ctrl+C in Terminal A when done — the subscription is removed automatically.
| Flag | Description |
|---|---|
--forward-to <url> |
Local URL to receive forwarded webhooks (e.g. localhost:3000/webhooks) |
--print |
Print each webhook payload to stdout |
--events <csv> |
Comma-separated list of event topics to forward/print (default: all) |
--port <int> |
Local port for the tunnel receiver (default: auto-assigned) |
--tunnel <provider> |
Tunnel backend: cloudflare (default) or localtunnel |
At least one of --forward-to or --print is required.
listen is sandbox-only and refuses to run against production credentials.
All commands support multiple output formats via --output-format (-o):
./dwolla customers list -o json
./dwolla customers list -o yaml
./dwolla customers list -o table
./dwolla customers list -o pretty # defaultFilter output with --jq:
./dwolla customers list -o json --jq '.[].id'
./dwolla workflows list -o json --jq '.[].name'| Flag | Description |
|---|---|
--client-id |
OAuth2 client ID |
--client-secret |
OAuth2 client secret |
--token-url |
OAuth2 token endpoint |
--server-url |
Override API base URL |
--output-format, -o |
Output format: pretty, json, yaml, table, toon |
--jq, -q |
Filter output with a jq expression |
--debug, -d |
Log full request/response to stderr |
--dry-run |
Print the request without executing it |
--no-interactive |
Disable interactive prompts |
--timeout |
HTTP timeout (e.g. 30s, 5m) |
./dwolla exploreBrowse all commands interactively without memorizing syntax.
When the CLI detects it's running inside an AI coding agent (Claude Code, Cursor, Codex, and others), it automatically switches to agent mode: token-efficient TOON output, structured JSON errors with recovery hints, and no interactive prompts.
CLAUDE_CODE=1 ./dwolla workflows list # TOON output
FORCE_AGENT_MODE=1 ./dwolla customers list # force it on anywhere
CLAUDE_CODE=1 ./dwolla customers list --agent-mode=false # force it offSee docs/agent-mode.md for the full list of detected
environments, structured error types, and recommended patterns.
| Symptom | Fix |
|---|---|
authentication failed (401) |
Run ./dwolla whoami to see which credentials/environment are active. Confirm DWOLLA_CLIENT_ID/DWOLLA_CLIENT_SECRET and that DWOLLA_TOKEN_URL points at the right environment. |
| Commands hit production unexpectedly | The CLI defaults to sandbox. Check DWOLLA_TOKEN_URL / DWOLLA_ENV; trigger and listen refuse to run against production. |
listen can't start a Cloudflare tunnel |
Install cloudflared (brew install cloudflared) or let the CLI download it, or force the fallback with --tunnel localtunnel. |
422/400 on a mutating command |
Re-run with --dry-run to inspect the request body before sending. |
| macOS won't run the downloaded binary | xattr -d com.apple.quarantine ./dwolla (code signing is pending). |
| Need to see the raw request/response | Add --debug — auth headers are masked. |
- Command reference — every command and flag (generated).
- Workflows guide — built-in catalog, flag matrices, and the custom workflow JSON format.
- Agent mode — structured output and errors for AI agents.
- Sample workflows — ready-to-run custom workflow files.
Regenerate the command reference after changing commands:
go run ./cmd/gendocs docs/commands.mdThe Dwolla CLI is in public beta. Please share feedback, bugs, or suggestions by opening an issue on this repo.
Licensed under the Apache License, Version 2.0. See LICENSE for the full text.