The twelve payment statuses, what each means, and which transitions exist.
A payment is one attempt to collect money from a customer. It moves through a fixed state
machine — no transition outside this table ever happens, and every transition is recorded in a
timeline you can read back.
The statuses
Status
Meaning
Terminal?
created
The payment exists; no customer number yet (links and hosted checkout start here)
no
initiated
We are contacting the provider
no
awaiting_customer
The PIN prompt is on the customer’s phone
no
awaiting_otp
The provider asked for a one-time code
no
awaiting_code_use
The customer received a code to use at a till
no
completed
The provider confirmed the money moved. The only status that credits your balance
yes*
failed
The provider definitively said no (declined, insufficient funds)
yes
expired
The customer never acted and the window closed
yes
unknown
We asked, the network didn’t answer. Not a failure. We keep asking
no
unresolved
Still unknown after 24 hours of retries; a human takes over. Your balance is not credited
yes*
settled
Completed and paid out to your bank in a settlement batch
yes
refunded
Completed, then refunded in full
yes
* completed later becomes settled or refunded; unresolved can be manually resolved.
What you should key on
Fulfil on payment.succeeded (the webhook) or on reading completed/settled — never on
anything else.
Tell the customer “payment failed” only on failed or expired. Treat unknown as
“still confirming” — here’s why.
A payment’s full history is in the dashboard timeline: every state, when, and what caused it.
Amounts on the wire
All amounts are integers in minor units (cents). USD 100.00 is 10000. There are no
floats anywhere in Lango, and there should be none in your integration either.
Field
Meaning
amount
The order amount — what the sale is worth
charged_amount
What the customer actually pays (order + their fees)
merchant_net
What lands in your balance
fees[]
Every fee, itemised, with its bearer
charged_amount − merchant_net is always exactly the commission. To the cent, every time — the
invariant is enforced in our ledger, not just documented here.
The unknown state
The one page that prevents double charges. Read it even if you read nothing else.
What unknown means
Mobile-money networks time out, drop callbacks, and answer late — routinely, not exceptionally.
When Lango asks the provider “did this payment go through?” and the network doesn’t answer,
there are exactly three truths and we don’t know which one holds:
The customer paid, and the confirmation was lost.
The customer didn’t pay.
The customer is still deciding.
Any system that maps this situation to failed is guessing — and when it guesses wrong,
the customer pays twice. Lango refuses to guess. The payment enters unknown and we chase
the answer: we re-query the provider on a backoff schedule (10 seconds, 30 seconds, 2, 5, 15,
30 minutes, then hourly) until it resolves or 24 hours pass.
What you should do
Show the customer: “We’re confirming your payment with EcoCash. You’ll get an SMS from
EcoCash if it went through. Please don’t pay again.”
Don’t retry the charge. If you retry with the same Idempotency-Key, you safely get the
same payment back (good). If you retry with a new key, you’re asking to charge twice (bad).
Wait for the webhook. When the truth arrives you’ll get payment.succeeded or
payment.failed like any other payment. There is no webhook event for entering unknown
— it isn’t an outcome, it’s the absence of one.
If 24 hours pass
The payment becomes unresolved and a human at Lango takes over, checking provider statements
directly. Your balance is not credited for unresolved payments — money you can’t prove is
money you don’t have. If the money did move, resolution credits it retroactively and you’re
notified; if it didn’t, the payment is failed with a paper trail.
This is rarer than it sounds — most unknown payments resolve within the first minute — but
when it happens, it’s handled in the open rather than silently absorbed.
Fees, preview and bearers
Commission, who bears it, IMTT, VAT — and the preview endpoint that quotes them with the live engine.
Lango’s differentiator is legibility: every charge is computed by one engine, itemised on the
wire, and quotable in advance. A quote cannot drift from a charge because they share the code.
The fees
Fee
What it is
Who sets it
Commission
Lango’s fee on each collection (e.g. 3%)
Your agreement
VAT
15% inside the commission (not on top), broken out for your returns
Statute
IMTT
Tax on the payout bank transfer — 2% (USD) at time of writing
Statute
Transfer fee
The bank’s flat fee per settlement transfer
The bank
Statutory rates change by government notice; Lango applies the rate in force on the day, and
your settlement advice names the rate used.
Bearers: who pays the commission
Your fee configuration decides who bears commission:
Customer bears — the fee is added at checkout as its own line before they confirm.
A USD 100.00 sale charges the customer 103.00; you receive 100.00.
Merchant bears — the customer pays the sticker price; commission comes out of your side.
The customer pays 100.00; you receive 97.00.
Split — a percentage share of each.
The invariant, enforced in the ledger on every payment:
charged_amount − merchant_net = commission, exactly, in integer cents.
Preview: quote before you charge
POST /v1/preview runs the same engine with no money movement:
IMTT is grossed up correctly. To land net in your bank when the tax is charged on the
transfer itself, the engine solves the gross-up: on a 500,000 payout at 2%, IMTT is 9,804 —
not 10,000. We publish the arithmetic; check it.
warnings is honest. If a rate in the calculation hasn’t been contractually confirmed
yet, the preview says so instead of pretending.
Use preview to show customers total cost before they commit, or to build your own pricing
calculators — ours (lango.co.zw/pricing) is this endpoint.
Idempotency
Why every POST takes an Idempotency-Key, and how retries become safe.
Networks fail after the server acted as often as before. If you POST a charge, the connection
drops, and you retry — did you charge once or twice? With idempotency keys, the question
disappears.
How it works
Every POST that creates something takes an Idempotency-Key header (it’s required on
POST /v1/payments). The key is any string up to 255 characters that uniquely identifies the
attempt — an order id is usually perfect.
Same key + same body → you get the original response back, replayed. No second
charge, ever. Safe to retry as hard as you like.
Same key + different body → 409 idempotency_key_reused. You almost certainly have a
bug; we refuse rather than guess which body you meant.
Same key while the first attempt is still running → 409 request_in_progress. Back off
and retry; you’ll get the replay.
Keys are held for 24 hours.
References are a second net
Independently of idempotency, every payment carries your reference, and a reference is
unique per merchant. Reusing one returns 409 reference_in_use with the existing payment
to fetch instead. Between the two mechanisms, “charged twice because we retried” is not a
failure mode a Lango integration has.
The rule of thumb
Generate the key where the intent originates (your order id, your invoice number), not at
the HTTP call site. Then any layer of your stack can retry safely — job queues, load balancers,
a user mashing F5 — and the worst case is always the same response twice, never the same charge
twice.
Webhooks and signatures
Signed deliveries, the retry schedule, and how to verify a Lango-Signature.
Webhooks tell your server what happened without polling. Every delivery is signed; verify the
signature and you can trust the news came from Lango and wasn’t replayed.
Events
Event
Fired when
payment.succeeded
The provider confirmed the money moved
payment.failed
The provider definitively declined
payment.expired
The customer never acted
payment.unresolved
24 hours of unknown; a human is investigating
refund.succeeded
A refund completed
refund.failed
A refund could not be completed
settlement.paid
A settlement batch paid out to your bank
There is no event for unknown — it isn’t an outcome, and an event would tempt you to
treat it as one.
Respond with any 2xx within 10 seconds to acknowledge. Do the real work after
responding, not before.
On failure we retry at 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours, and
stop after 24 hours. An endpoint that keeps failing is marked degraded in your dashboard.
Deliveries can arrive out of order and, rarely, more than once. Key your handling on
the event id (store processed ids) and on the payment’s current state, not on arrival
order.
Missed something?
Every event is also in the log: GET /v1/events lists them, and
POST /v1/events/{id}/replay re-queues delivery to your endpoints. After an outage on your
side, replay the window you missed instead of reconciling by hand.
Settlements
How collected money reaches your bank, and how to read the advice.
Money Lango collects for you accrues in your balance (per currency, visible in the
dashboard and at GET /v1/balance). On your settlement cycle, the balance pays out to your
verified bank account as a settlement.
The flow
Completed payments credit your balance the moment the provider confirms them.
On your cycle, Lango proposes a settlement batch covering your payable balance above the
minimum threshold.
The batch passes Lango’s internal controls — including the solvency check (the client
account must cover every merchant’s balance, always) and dual approval (no single
person at Lango can release money).
The transfer lands in your bank; each settled payment’s status becomes settled; you
receive a settlement.paid webhook and an advice.
The advice
Every settlement comes with an advice (PDF and CSV) that itemises:
every payment in the batch — id, reference, date, gross, commission, net;
every deduction by name and rate: commission, VAT within it, IMTT at the rate in force,
the bank transfer fee;
the arithmetic from gross to the amount on your bank statement, to the cent.
If your bookkeeper can’t reconcile a Lango advice against the bank statement in one sitting,
that’s a bug — report it.
Notes
Currencies never mix. USD collections settle in USD, ZWG in ZWG, as separate transfers.
Refunds reduce your payable balance; a refund after settlement is recovered from the
next batch.
Bank account changes have a 48-hour cooling-off period before they can receive
settlements — an anti-fraud control protecting you, not us.
Currencies
USD and ZWG, integer minor units, and why nothing is ever summed across currencies.
Lango processes USD and ZWG. Three rules keep multi-currency handling honest:
1. Amounts are integers in minor units
USD 100.00 is 10000. ZWG 312.04 is 31204. Always two implied decimal places, never
floats. Send amount: 10000, read amount: 10000 — formatting is a display concern.
When displaying, put the currency code first (USD 1,240.50), because a bare $ is ambiguous
in Zimbabwe.
2. Currencies never blend
Balances, settlements, reports and totals are always per currency. Lango never sums USD
and ZWG into one number, at any exchange rate, anywhere — and your integration shouldn’t
either. A “total revenue” figure spanning both currencies is a fiction the moment the rate
moves.
GET /v1/balance:
{
"available": [
{ "currency": "USD", "amount": 125000 },
{ "currency": "ZWG", "amount": 3120400 }
]
}
Two entries, two truths — deliberately not one.
3. A payment lives entirely in one currency
The order, the commission, the customer fee, the settlement — all in the payment’s currency.
There is no cross-currency payment and no automatic conversion. If you price in USD and want
to accept ZWG, create ZWG payments at your own rate, on your own terms, explicitly.
Statutory rates differ by currency
IMTT and other statutory charges can carry different rates per currency (and change by
government notice). Preview quotes the rate in force; your settlement
advice names the rate that was applied.
Payment links and QR codes
Collect without writing code — a link in a WhatsApp message or a QR on the counter.
A payment link is a URL that turns into a checkout the moment a customer opens it. No website,
no integration — and the same API object when you do want to automate.
From the dashboard
Dashboard → Payment links → New payment link. Choose:
Single use — collects one payment, then stops. Right for invoices. Reopening a paid link
shows the same payment, so a customer can’t pay an invoice twice.
Reusable — takes a payment from everyone who opens it. Right for a printed QR on the
counter; every scan is a fresh payment.
Every link comes with a print-ready QR code and a copy button.
Send url to the customer. When they pay, the payment appears in your dashboard and your
webhooks with reference: "INV-2024-091" — the link’s reference becomes the payment’s
reference, which is how you match it to the invoice.
Bulk
Creating a month’s invoices? One call, up to 100 links:
Unique per merchant; becomes the payment’s reference
description
—
For your own lists; shown nowhere public
expires_at
never
ISO 8601; an expired link refuses politely
What the customer sees
The link opens Lango’s hosted checkout: the amount, your business name, any customer-borne fee
as its own line, and an EcoCash number field. It’s built for 3G and small phones, and it never
shows “failed” while the network is still deciding. You never handle the customer’s details.
The buy button
One script tag and a div — a payment button on any website, even one you can't really edit.
The buy button puts a Lango payment button on any page — a WordPress site, a
school’s ancient CMS, a Google Sites page. There is no key in the page and
nothing to steal: the button rides on a payment link,
and a link token only lets a customer pay you.
Setup
Create a reusable payment link (dashboard → Payment links → New →
Type: Reusable), or a single-use one for a specific invoice.
That’s the entire integration. Clicking opens Lango’s hosted checkout for
that link; the customer approves on their phone; the payment lands in your
dashboard and webhooks with the link’s reference.
Attributes
Attribute
Required
Default
Notes
data-lango-link
yes
—
The link’s token (plk_...), from dashboard or API
data-lango-label
no
Pay with EcoCash
The button text
data-lango-amount
no
—
Display only, e.g. USD 10.00 — shown after the label
Multiple buttons on one page work; the script renders every
[data-lango-link] it finds.
Notes
The script is ~2KB, dependency-free, and never touches your page beyond
the divs you mark. It makes no network requests of its own.
The amount charged is always the link’s amount — data-lango-amount
is a label, so a tampered attribute can mislabel but never mischarge.
Want the price to come from your page dynamically? That’s the inline
modal, which arrives after launch — for now create one link per price
(bulk API makes that painless).
Hosted checkout
Create a payment from your backend, send the customer to checkout_url, and let Lango handle the rest.
Hosted checkout is the integration most stores want: your backend creates the payment, the
customer pays on checkout.lango.co.zw, and Lango handles the prompt, the polling, the
timeouts and the copy — including the states that protect you from double charges.
Omitting customer.msisdn is what makes it hosted: the customer enters their own number on
the checkout page, and the charge happens there.
Redirect the customer to the checkout_url in the response
(https://checkout.lango.co.zw/c/tok_...).
The customer pays: they see your name, the amount, any customer-borne fee as its own
line; they enter their EcoCash number; the PIN prompt lands on their phone with a visible
countdown.
You hear the outcome on your webhook (payment.succeeded / payment.failed), and the
customer is offered the return_url back to you.
Rules worth knowing
Fulfil on the webhook, not the redirect. A customer can close the browser after paying;
the webhook always arrives.
The checkout page expires unpaid payments after their window; the payment ends
expired, with an event.
The page shows your trading name — set it in Dashboard → Settings before going live.
The checkout token in checkout_url grants access to that payment’s checkout view and
nothing else. It cannot read your account.
What checkout does that you don’t have to
Zimbabwean number formatting and validation (263 7x ...).
The waiting screen with countdown, “check your phone”, and the masked number.
Timeout handling that distinguishes “declined” from “no answer yet” —
the unknown discipline — with copy that stops customers paying
twice.
Works with JavaScript disabled; under 50KB; no third-party scripts for a strict CSP.
Direct API
Charge a known customer number in one call — for apps and platforms that own the whole flow.
If you collect the customer’s EcoCash number in your own product (a delivery app, a utility’s
own portal), charge directly: one call with customer.msisdn, and the PIN prompt goes to the
phone immediately.
The response is awaiting_customer: the prompt is on their phone. From here:
Listen for the webhook (payment.succeeded / payment.failed / payment.expired) —
the right way; or
PollGET /v1/payments/{id} — acceptable for low volume; back off to every few seconds.
Your responsibilities in this mode
Because your UI owns the experience, the checkout page’s protections become your job:
Show the fee before charging if your customer bears it. charged_amount in the create
response (or preview beforehand) is the number they must see. Surprising
people on the PIN prompt kills conversion and trust.
Handle unknown honestly. “We’re confirming with EcoCash — don’t pay again.” Never
“failed” while it’s still unknown. The whole page on this is
required reading for direct integrations.
Tell them to check their phone — with the masked number (customer_msisdn_masked in
the response) and a countdown matching the payment’s expires_at.
Validate numbers loosely, submit strictly. Accept 0771 234 567 from humans; send
263771234567. Lango normalises common Zimbabwean formats and rejects what can’t be a
Zimbabwean mobile number, with a human-readable message you can show verbatim.
Metadata
metadata stores up to your own JSON object on the payment and comes back on every read and
webhook — use it for your internal ids so reconciliation is a lookup, not a join.
Listing and pagination
GET /v1/payments?limit=25&starting_after=pay_... pages newest-first. Filters: status,
method, currency, and search (matches references). The dashboard’s payment list is this
endpoint.
Sandbox and test numbers
Ten deterministic numbers that force every outcome — break it before your customers do.
Test keys (sk_test_) hit the same API against a simulated provider. No real money can move on
a test key, ever — the environments are separated at the key level, not by a flag you could
get wrong.
The test numbers
The last two digits of the customer number choose the scenario. Any other suffix behaves
as success.
Number
Scenario
What happens
Final status
263771000001
Success
Customer approves promptly
completed
263771000002
PIN declined
Customer rejects the prompt
failed
263771000003
Customer timeout
Prompt delivered, never answered
expired
263771000004
Insufficient funds
Wallet can’t cover it
failed
263771000005
Invalid number
Provider rejects the MSISDN
failed
263771000006
Duplicate reference
Provider claims it has seen this reference
failed
263771000007
Delayed answer
Provider answers “pending” twice, confirms on the third query
completed
263771000008
Callback never arrives
Success, but the provider never calls back — our resolver discovers it by asking
completed
263771000009
Provider HTTP 500
The charge call itself errors — payment goes unknown, resolves on query
unknown → completed
263771000010
Connection timeout
Nothing ever answers — the full unknown journey
unknown → unresolved
What to actually test
Before going live, walk your integration through at least:
The happy path (...01): create → webhook → fulfil.
A definitive failure (...02): your UI says “declined”, your books say nothing owed.
The delayed answer (...07): your code waits for the webhook rather than timing out at
5 seconds and guessing.
The unknown path (...09): your UI says “confirming — don’t pay again”, not
“failed”. This is the test that protects your customers from double charges.
A retry with the same Idempotency-Key: confirm you get the same payment back, not a
second one.
Forcing outcomes without numbers
POST /v1/test/simulate (test keys only) advances a payment directly — useful in automated
test suites where you don’t want to wait for the resolver’s schedule:
Test-mode webhooks deliver and sign exactly like live ones. Point an endpoint at a tunnel
(ngrok or similar) during development, and use POST /v1/events/{id}/replay to re-fire any
event while debugging your handler.
Error codes
Every error, its cause, and the remedy — in one searchable table.
Every error is JSON in one envelope, with a message written for humans and a request_id you
can quote to support:
{
"error": {
"type": "invalid_request",
"code": "parameter_invalid",
"message": "amount: Number must be greater than 0",
"param": "amount",
"request_id": "req_01J8XQ7M4K..."
}
}
Types
type
HTTP
Meaning
invalid_request
400
The request itself is wrong — fix and resend
authentication_error
401
Missing or invalid API key
permission_error
403
Valid key, missing scope
not_found
404
No such object for this merchant
idempotency_conflict
409
Key reuse conflict — see below
rate_limited
429
Slow down; retry after a pause
api_error
500
Our fault. Safe to query, then retry
Codes
Code
Cause
Remedy
parameter_invalid
A field failed validation; param names it
Fix the named field; the message says what’s wrong
idempotency_key_required
POST /v1/payments without an Idempotency-Key header
You have two different requests sharing a key; give each intent its own key
request_in_progress
Same key while the first attempt is still executing
Back off briefly and retry; you’ll get the original response replayed
reference_in_use
This merchant already has a payment with this reference
Fetch the existing payment instead of creating a duplicate
msisdn_invalid
Not a Zimbabwean mobile number
Normalise to 2637XXXXXXXX; show the error message to the user verbatim
resource_missing
The id doesn’t exist, or belongs to another merchant
Check the id and which key (test vs live) you’re using — objects don’t cross environments
link_expired
The payment link’s expires_at has passed
Create a fresh link
link_used
A single-use link was already paid and its payment can’t be resolved
Create a fresh link; investigate the original payment in the dashboard
refund_amount_invalid
Refund exceeds what remains refundable
Check prior partial refunds with GET /v1/refunds/{id}
rate_limited
Too many requests
Exponential backoff; respect any Retry-After header
internal
Something failed on our side
Query the payment before retrying — the charge may have succeeded — then retry with the same idempotency key
The retry rule
For 5xx and network failures on a POST: query first, then retry with the same
Idempotency-Key. That order guarantees you never create a duplicate even when our answer
was lost on the wire. For 400-class errors, retrying without changing the request will never
help.
Going live
The verification checklist, live keys, and launch checks.
Test keys are yours from registration. Live keys are issued — never self-served — once your
business is verified. Here is the whole path.
Verification checklist
In Dashboard → Settings, complete:
Business details — legal name, trading name, address, contact.
Documents — certificate of incorporation, CR14, proof of address, and a director’s ID.
Reviewed by a human at Lango, usually within two business days.
Bank account — where settlements go. We verify the account name matches your legal
name; a name mismatch is a hard stop, not a warning. New and changed accounts have a
48-hour cooling-off before they can receive settlements.
The agreement — your fee schedule and terms, accepted in the dashboard by a director.
When all four are green, Lango review flips your account to live and live keys appear in
Dashboard → Developers.
Launch checks
Before pointing customers at it:
Live key stored in your secrets manager — never in code, config files in git, or client-side bundles.
Webhook endpoint deployed, signature verification on, and subscribed to at least
payment.succeeded, payment.failed, payment.unresolved, settlement.paid.
Idempotency keys derived from your order ids, not generated per HTTP attempt.
Fee bearer configuration checked with a preview — the number your
customer sees must match what they’re charged.
A small real payment made and refunded, end to end, by you.
Key hygiene
Keys are shown once at creation and stored hashed — copy them then or roll them.
Scope keys to what each system needs (payments:read for your reporting job, not your full key).
Rotate by creating the replacement first, deploying it, then revoking the old key —
revocation is immediate.
If a key may have leaked, revoke it now and rotate; then check GET /v1/events for
anything you don’t recognise.
After launch
Watch the dashboard’s home attention panel for the first days: it surfaces failing webhooks,
unresolved payments, and anything needing action. Settlement advices should reconcile against
your bank statement to the cent — if one doesn’t, that’s our bug: support@lango.co.zw with the
advice number.
Changelog
Every API change, dated. Additive changes ship freely; breaking changes get a version and a migration window.
Lango’s compatibility promise: additive changes (new fields, new endpoints, new event
types, new optional parameters) ship without notice — build your parsers to ignore fields they
don’t know. Breaking changes get a dated entry here, a migration window, and direct email
to affected merchants.
2026-09-12
AddedPOST /v1/payment_links, POST /v1/payment_links/bulk, GET /v1/payment_links —
payment links as API objects, with QR codes in the dashboard.
AddedPOST /v1/pricing/preview — unauthenticated pricing quotes at the platform’s
published rates (the marketing calculator’s endpoint).
2026-09-11
Added the public API v1: payments, refunds, preview, balance, events, webhook
subscriptions.
Added sandbox simulation (POST /v1/test/simulate) and the ten deterministic test
numbers.
Embeddable checkout
Let customers pay in a popup on your own site with one script and a publishable key — no redirect, no backend required.
The embeddable checkout opens Lango’s hosted pay page in a modal on your own
site. The customer never leaves your page; when they finish, the popup tells
your page and closes. It needs one script and a publishable key — no
backend required.
1. Get a publishable key
In your dashboard, go to Developers → Keys → Publishable keys and create one.
A publishable (pk_) key is safe to put in a web page. Unlike a secret
sk_ key it grants no read access and cannot move money — the most it can do is
start a checkout that can only ever pay you. Lock each key to the domains where
your button lives (its allowed origins); the popup will only open on those
sites.
2. Add the button
Drop in the script once, then mark any element with data-lango-checkout:
data-lango-amount is in minor units — 10000 is USD 100.00. That is the
whole integration: a click opens the popup, the customer approves on their
phone, and you get the result.
Attribute
Required
What it is
data-lango-key
yes
Your publishable pk_ key
data-lango-amount
yes
Amount in minor units (cents)
data-lango-currency
no
USD (default) or ZWG
data-lango-reference
yes
Your order reference
data-lango-return-url
no
Where to send the customer after paying
3. Or call it yourself
For full control, call Lango.checkout(...) from your own code and react to the
result:
With the button above, the amount lives in the page, so a determined customer
could change it before paying. For anything that matters, create the checkout
on your server with your secret key and hand the popup the resulting token —
the amount is then fixed server-side and cannot be edited:
You can also skip the popup entirely and just redirect the customer to the
returned checkout_url.
How it’s kept secure
The popup is an iframe served by Lango, and it will only render on the
origins you listed on the key (frame-ancestors, enforced by the browser).
The publishable key can start a checkout and nothing else. Your secret
keys never touch the page.
Confirm every payment from your backend the same way you always do — the
payment.succeeded webhook is the source of truth, not
the popup.
Reacting to the result
onSuccess fires when the payment completes. Treat it as a UI cue only —
fulfil the order from the webhook, which is signed and can’t be spoofed.
A payment that is still confirming is unknown, never failed: see
the unknown state.
SDKs & plugins
Official Lango libraries for PHP, Node and Python, plus a WooCommerce plugin — so you don't hand-roll signing, idempotency or retries.
Lango ships official libraries so you never hand-roll the fiddly parts — request
signing, idempotency keys, webhook signature verification, and the retry
rules around the unknown state. Every library speaks the same
API and follows the same behaviour described throughout these docs.
Libraries
PHP — a small, dependency-light client for creating payments, reading
their status, issuing refunds, and verifying webhook signatures.
Node.js — a typed client with the same surface for JavaScript and
TypeScript back ends.
Python — the same client for Django, Flask, FastAPI and scripts.
WooCommerce plugin — drop-in EcoCash checkout for WordPress/WooCommerce
stores, no code required.
What every library gives you
One call to charge, one call to read. The client mirrors the API: create a
payment, then poll or wait for the webhook.
Idempotency by default. Every create takes an Idempotency-Key, so a
retried request after a network blip returns the original result instead of
charging twice.
Webhook verification in one line. Pass the raw body and the
Lango-Signature header and the library checks the HMAC for you — never trust
an unverified webhook.
Honest states. A payment is unknown until we know, never failed while
the network is still deciding. The libraries surface that faithfully so you
don’t mark an order failed by mistake.
Getting the libraries
Install commands land here as each library publishes to its registry
(Packagist, npm, PyPI and the WordPress plugin directory). In the meantime, ask
us for access and start against the API reference and the
five-minute quickstart — the SDKs are thin wrappers over exactly
those calls.
Fulfil orders from the signed payment.succeeded webhook,
which every library helps you verify — it is the source of truth, not the
browser.