Changelog
What's new on the Wajub platform, by date, version, and product.
All notable changes to the Wajub API and products. The current API version is
2026-10-11 (versioning). Additive changes require
no action; breaking changes are released under a new date.
2026-10-12
NEW API
API version 2026-10-12 is the version of new accounts. An existing account keeps its version
and reaches this one with X-Wajub-Version; webhooks still follow the current version,
2026-10-11. Its breaking changes:
- A channel of
GET /channelsandGET /providersno longer hascurrency, which readXAFfor every channel. currencyis required onPOST /transfersand on each line ofPOST /transfers/batch. Earlier versions still default toXAF: send it whatever your version.- A payment link has no
category_id:POSTandPUT /linksno longer take it,GET /linksno longer filters on it, and a link no longer returns it. - A transfer or a refund stopped before it was sent reads
cancelled, with the reasonmerchant_rejectedorcancelled_by_wajub, and?status=failedno longer lists it. Earlier versions readfailed. Webhooks follow the current version: such a stop is sent astransfer.failedorrefund.faileduntil 2026-10-12 becomes current, then astransfer.cancelledorrefund.cancelled.
API Payments
-
POST /payments/{id}/splitscharges instalments in live, on Mobile Money. Each call charges the next instalment; the payment ispartialuntil the last one, and a declined instalment keeps the paid ones. Each paid instalment is credited to your balance as it clears, with its share of the Wajub fee, in live as in sandbox; it is paid out to your payout account like any payment, and the Dashboard's payment page shows its hold. Split payments -
New
POST /payments/{id}/close: close a payment paid in part, keeping (outcome: keep) or refunding (outcome: refund) what was collected. It becomessucceededfor that amount, and refunds are measured against it.DELETE /payments/{id}answers409 payment_partly_paidon such a payment. Close a payment -
Sandbox no longer splits a Mobile Money charge above the per-transaction ceiling on its own: it answers
422, as live does. Once a payment has an instalment plan, every later charge, the payment page's included, takes the next instalment, and another plan answers422. -
New
POST /payments/{id}/confirm: finish a charge's OTP, PIN, phone, birthday, address or provider-window step from your server with your secret key. It answers as the charge does:200,202with the next step, or402withattempts_remaining. Confirm a step -
Every announced Mobile Money channel now charges in live: T-Money, Tigo Pesa, HaloPesa, Free Money, Vodafone Cash (Ghana and Egypt), AirtelTigo Money, Zamtel Kwacha, TNM Mpamba, Africell DRC, Moov Money in Gabon, Burkina Faso and Mali, and MTN in Liberia and Guinea. Djamo and Capitec Pay take any number. Phone numbers
-
accept.channelstakesmobilefor every Mobile Money operator. The payment object returnsbearer,expires_atandlink, the payment link it came from. Choosing what a payment accepts -
A card charge takes
card_binat the root of the body, like every field ofdata. In sandbox, the test card is read at the root as well (card.number,card.holder). -
A charge on a payment that is already
processinganswers409witherror_code: payment_in_progress. The answer -
A declined charge's
402carriespayer_message, a sentence you can show the payer as it is. Status codes -
callbackmust be HTTPS (http://localhostandhttp://127.0.0.1accepted in sandbox).metadata.cancel_urlis deprecated: the callback is the one return for every outcome. Hosted Checkout -
GET /payments?reference=lists the payments created with exactly thatreference. A payment is read by itsid;referenceis yours and not unique, andIdempotency-Keyprevents duplicates. -
A failed payment carries
failure_reasonandfailure_message, on the payment and inpayment.failed. Failure Reasons -
A Shield block answers
403witherror_code: shield_blockedandblock_reference. Shield blocks -
An account whose verification is not approved cannot collect any live payment, whatever the provider:
402 merchant_not_verified. Compliance -
An unknown
customer_idonPOST /paymentsanswers422on that field, not404 Resource Not Found.operatorandsave_payment_methodare read at the root of a charge.GET /payments/optionsis documented and answers in the usual envelope. -
Sandbox charges like live: the failure numbers answer
402witherror_code: insufficient_funds, a local-format number is read in the channel's country, and every announced Mobile Money channel has a test number, Vodafone Cash Egypt (+2010…) and Orange Money Sierra Leone (+23276…) included. Capitec Pay has test numbers of its own (+2761…) that choose the outcome; any operator's test number still pays on it. A number that is not a test number gets a422listing the test numbers of the channel's operator. Test scenarios -
GET /channels:min_amountandmax_amountare numbers in the major unit (they were strings a hundred times larger: remove any division by 100), andliveandchargesay what they promise. A channel's per-transfer ceiling is read in the major unit too. Coverage -
Every
422carriesstatus: "Unprocessable Content"and anerrorsobject; a refusal that concerns no single field puts its message undererrors.request. Status codes -
List filters are checked instead of ignored, each with a
422on its field:- every numbered list, tax codes and jurisdictions included: a
pagethat is not a whole number from 1; GET /linksandGET /invoices: aper_pageoutside 1–100, which used to be brought into range, and an unknownsort_byorsort_dir;GET /invoices: an unknownstatusorpayment_status, and adate_from,date_to,due_date_fromordue_date_tothat is not a date or ends before it starts (it answered500);GET /links: an unknowntype;GET /events: atypeno endpoint receives (both answered an empty list);GET /channels: an unknowncountry;GET /countries: an unknownregion;GET /currencies: atypeother thanfiat,cryptoorall, read whatever its case, sotype=CRYPTOlists the crypto currencies where it listed the fiat ones.
GET /linksnow orders by the requestedsort_byandsort_dirfirst, newest first only among ties. Pagination - every numbered list, tax codes and jurisdictions included: a
-
Rate limits are counted per environment: a sandbox burst no longer uses the live budget. Reads of a transfer no longer count against the payout write bucket. A
429or a version400now carries the CORS headers,X-Wajub-Versionis allowed in a preflight, andRetry-After,X-RateLimit-ResetandX-Wajub-Versioncan be read from a browser. A429from a tighter throttle, such as payout writes, answers like the main limiter:typenames it (payout_writes), andretry_afterandRetry-Aftergive the real wait.X-RateLimit-Resetgives the end of the current window. Rate limits -
GET /reports inenvthe environment of the key you call it with./healthand/health/readyreturn the API version. Monitoring -
Identity is available on Growth, Scale and Enterprise; Pay as you go answers
403. Identity -
API reference: every parameter is described with a real example, and the request fields that accept
nullsay so. The invoice, refund, statement and receivable pages show their success answer, and the lists of refunds, beneficiaries, links, accounts and webhook endpoints a complete item. AGET's filters are query parameters only,beneficiaryonPOST /transferstakes anidor an object, and dispute paths read{id}. The OpenAPI spec declares reusable schemas (Payment, Transfer, Refund, Customer, Beneficiary, Invoice, Link, Account, Event, WebhookEndpoint, Balance, Error) that its responses reference, so a client can be generated from it, andGET /eventslists the 77 event types it takes astype. The resource overview pages are available in French. API reference
API Payouts
- A refused payout answers
422with anerror_codenext tomessageanderrors, onPOST /transfers, on each refused line of a batch and onPOST /accounts/{id}/payouts. A batch line whose beneficiary does not exist saysbeneficiary_not_found, not "safe to retry". Payout refusals - Each batch line gets the checks of
POST /transfers: the currency's bounds (amount_out_of_range), and a beneficiary named by itsidonly (beneficiary_not_found). - A payout names its operator's channel (
ke.mpesa,eg.vodafone…). The one generic channel iscm.mobile, which works outcm.mtnorcm.orangefrom the number: in live from Cameroon's real numbering plan, as payments do, in sandbox from the test prefixes. Any other{country}.mobileis refused as an unknown channel. Beneficiaries - A deleted beneficiary stays on its transfers and its statement, with
deleted: true. Paying a deactivated beneficiary answers422 beneficiary_inactiveinstead of404. - A live payout that waits more than 10 seconds answers
429 payout_queue_busywithRetry-After: send it again with the sameIdempotency-Key. Limits - Payouts in every currency count toward your daily payout cap, payouts held for review included,
in the API and in the Dashboard. A sandbox payout whose
wallet covers the amount but not the fee is refused with
insufficient_balance. - An older Dashboard payout still awaiting your confirmation, which reads
pendingin live, can be confirmed again; it may be held inreviewbefore it leaves. After a refusal, the Confirm button can be pressed again. Transfer statuses - Documentation: a refused payout keeps the beneficiary you passed inline. The Transfers reference
and statuses follow the real response, Balance documents
disputed,in.transitandhold, the holds table has a "Wajub restricted your account" row, and the routes sharing the payout write bucket are listed. Transfers, Balance
API Invoices
- A payment is attached only to an invoice of its own account.
GET /invoices:amount_minandamount_maxare read in the major unit. The invoice gainscustomer, thecus_id; a payment made on an invoice's page names it ininvoice.invoice.paidandinvoice.payment_succeededare delivered.POST /invoices/{id}/sendanswers422when the invoice has nobody to send it to and400for an invoice already paid, cancelled or refunded.mark-paidrefuses a draft, an invoice already paid and an amount of zero. A paid invoice turnsrefundedonce its online payments are refunded in full; an invoice paid in part online does too, withinvoice.updated. Invoices- Invoices sent through the API take their number from your account's numbering, the sequence the
Dashboard uses: your prefix (
INV-by default), the year and a four-digit counter that restarts each year (INV-2026-0001). A number is unique within your account. - New
languagefield (froren) onPOSTandPUT /invoicesand in every invoice response. It sets the language of the e-mails, reminders and PDF, and the invoice page opens in it (add?locale=fror?locale=ento switch). Without it, an invoice takes the linked customer's preferred language, else your account's (Settings → Regional → Language), elseen; invoices created in the Dashboard record theirs by the same rule. - The invoice PDF is public once the invoice is sent:
https://api.wajub.com/i/{id}/pdf, with?locale=fr|en.invoice.createdis sent once, with the lines and totals, and noinvoice.updatedwhile the invoice is built.PUT /invoices/{id}sends a singleinvoice.updatedwith the lines and totals it saved, and none when it changes nothing. sort_byonGET /invoicesnow orders the list, andsearchfinds invoices by the name and e-mail written on them.payment_dateonmark-paidbecomespaid_atwhenever the payment settles the invoice.currencyonPOSTandPUT /invoicesmust be a currency Wajub supports, otherwise422.- On an invoice with no e-mail, the address the payer gives on its page is saved as
customer_email, withinvoice.updated. An e-mail you set is never replaced. - A payment on the invoice page counts the Dashboard's credit notes: the invoice turns
paidonly once what was paid, minus the credit notes, covers the total. Invoices
Link Payments
- A payment link's
custom_urlmust be unique across Wajub, deleted links included (422). The link object returnssingle_use_linkand every other field you can set, and viewing a link no longer sendslink.updated. Payment Links GET /customers?country=filters on the customer's address country.PUT /customers/{id}keepsnameunless a name field is sent with a value. Customers
API Refunds Disputes
- A live refund that took money from your balance carries
ledger_balance_before,ledger_balance_afterandledger_balance_currency. A refund the operator refuses after it was sent staysprocessingwhile Wajub completes it another way, never back topending. A failed refund always carries afailure_reason, from one list in production and sandbox. Refunds refund.succeededis sent once the refund has left your balance and carriesledger_balance_beforeandledger_balance_after, sandbox included. A refund the provider refused and Wajub could not complete another way ends with the provider's reason;ops_failedonly when none is known. Handle refunds- A refund's
referenceis only ever yours: the provider's refund id is inprovider_reference. Empty refundmetadatais{}, and the sandbox refund search matchesreferencetoo. dispute.deadline_approachingis delivered two days before a dispute's deadline.dispute.closedfollowsdispute.losttoo. Disputes opened from Pocket get adue_dateseven days out, and the same reminder. Disputes- Dispute evidence needs a text or a file (
422). Evidence and message attachments are private files: every API answer that returns a dispute gives each file aurl, a signed link valid 30 minutes, and webhooks carryfile_pathonly. The Dashboard links them the same way and shows a file the payer attaches from Pocket in the dispute's thread. Disputes
Sync
- A lost dispute returns your commission by the connection's
commission_refund_policy, as a refund does, and sandbox moves the commission both ways.fee.receivedgives the commission in the pricing rule's currency. Account capabilities - While a connection's payments are suspended,
X-Syncreads work; only calls that move money are refused. A Relay connection carries no invitation date.POSTandPUT /accountsaccept the merchant'semail, a reminder keeps the link you sent valid, and a new link restarts the thirty days, as Resend invitation in the Dashboard now does.dispute.*events are copied to the platform. Onboarding POSTandPUT /accountsstore yourmetadataas you send it. Every account returns it ({}when empty);PUTmerges it, and a key sent asnullis removed. The account showspricing.min_fee, andGET /accounts?status=filters on the status the account shows,expiredincluded. Subaccounts- After onboarding, the merchant comes back to your
callbackwithaccount, theacc_…id, next tostateandstatus.callbackmust be HTTPS (http://localhostandhttp://127.0.0.1accepted with a sandbox key); a callback already stored keeps working. - At most 50 open invitations per environment, after which
POST /accountsanswers429; Relay connections do not count. A connection of the other environment answers404. A Relay connection has no authorization link:authorization_urlisnull, and/tokenanswers422. POST /accounts/{id}/payoutsreturns only the day's automatic payout, which may befailed. Payouts- Events the Dashboard sends carry
accountnext tosync_uid.account.updatedis sent once, with the whole account, whenPUT /accounts/{id}changes an account, whenPOST /accounts/{id}/tokenputs a cancelled invitation back topending, and when Wajub sends a Lite merchant's payout (last_payout_at). A save that changes nothing sends nothing,DELETEsendsaccount.deauthorized, andaccount.updatedcan be chosen on an endpoint again. Webhook events - Commission receivables:
transactionis the payment'sid(trx_…), the oneGET /payments/{id}reads, inGET /accounts/{id}/commission-receivablesand in theaccount.commission_receivable_recordedandaccount.commission_receivable_collectedwebhooks, on every API version.
Tax
- A refund reverses its share of the tax only when the payment recorded some, and it does so even after Tax is switched off or your plan stops covering it. Reports
- A payment's tax is recorded at the national rate, as
/tax/calculatecomputes it, and is not recorded while your plan does not cover Tax. Threshold alerts convert sales into the threshold's currency, and the EU threshold counts every member state. - The hosted payment page shows no tax line, and converted amounts carry no tax:
taxisnullin the session. Tax is computed when the payment is credited, never added to what the payer pays. - Customer tax ids are checked against the same formats as your own number, with or without
separators, and adding a number already on file checks it again (
200).gh.vatandgh.tinfollow the GRA's format (C0001234567, or a Ghana Card PIN); the old 12-charactergh.tinform is refused. Configuration - Tax registrations: a duplicate for the same country and state answers
422, a malformed id404, andPUTcan set a revoked registration active again./tax/calculateanswers422for an unknown country and404for an unknown customer, and removing a customer's tax id with a malformed id answers404instead of500. Tax API - Documentation: rounding examples corrected, reverse charge clarified, permissions and pagination documented.
Shield
- Sandbox payments never affect live risk scoring. Risk scoring
- Repeated declines on one card BIN at your business now count toward the risk score.
- The blocklist checks each value against its type and stores it in one form
(
+237 670 00 00 00and237670000000are one entry). An impossible value answers422, and removing an unknown entry answers404. Rules - A partial
PUT /shield/settingskeeps your stored Advanced switches./shield/statscounts reviews as the queue does and includes disputesunder_review. Test rules are still evaluated after the live rule that decides. A dispute accepted in the Dashboard weighs on the payer's reputation as one accepted through the API does. Shield API
Orchestration
- A route that keeps failing is paused for a few minutes, then tried again; a route that still fails stays paused longer. Waterfall fallback
- Routing skips a provider Wajub marks as in maintenance (
provider_in_maintenance), coming soon or disabled (provider_unavailable); only available and beta providers take payments. The routing simulator names each reason. Routing rules - Each attempt records its latency, shown in the payment timeline and per candidate in the routing log. A connection can be limited to chosen currencies, in the setup wizard and on its page. An approved Wajub rail starts at priority 0, like every other connection. Providers
- Dashboard alerts scoped to a channel or a rule, the rule impact preview and the advisor read the channel and rule each payment was routed by, and "Cascade recovered" counts only failovers. They count payments routed from this release on: earlier payments carry no routed channel or rule.
GET /channelsreturnscountries: []for a global channel instead of[""].- Documentation: the timeout to give your client (at least 90 s), client sessions and confirmations never fail over, the providers you can connect, and the international methods still to come on Coverage. Orchestration
Webhooks
payment.attempt_failedis sent when an attempt is refused at once and the payment ispendingagain.payment.expiredandpayment.cancelledare sent in sandbox as in live; a sandbox expiry no longer sendspayment.failed.- Every delivery attempt is signed with the endpoint's current secret, so a rotation covers the retries too.
link_payment_completeis no longer delivered (payment.succeededcovers it), andGET /eventslists only the types that are delivered.- In payloads, an empty object such as
metadatastays{}instead of[], when delivered, resent or listed. Thepayment.split.*events carry the full payment,payment_methodincluded. beneficiary.createdis also sent for payout accounts added in the Dashboard, andtransfer.processingonly once when a held payout is approved. Webhook events
Components CLI Dashboard
- Wajub.js 1.6.0:
onSuccess,onBreakdownandfetchSession()give amounts in major units (12.5for 12.50 EUR); remove any division by 100.onSuccesscarriesid, the payment's Wajub id. Back from the hosted checkout,referenceis that id andtrxrefyour reference. Inline, Done andclose()callonClose.CheckoutStategains four step states, every wrapper re-exports the core types, andWajubPay.create()is deprecated. API reference - Wajub.js 1.6.0: a hosted checkout that cannot load its session closes the overlay or removes the
inline frame right after
onLoadError, withoutonClose.submit()with no pay button to press callsonErrorwithnothing_to_submit. Each checkout listens only to its own iframe, so with two on a page each one receives only its own close and success. In Vue,<CheckoutEmbed>shows its loading state whenshow-loadingis absent. @wajub/svelte1.6.0 ships its components as source, compiled by your project's own Svelte 4 or 5, and they now carry prop types. A Svelte 5 project that forces runes on every file must leavenode_modulesout, asnpx sv createdoes. Svelte- Hosted checkout: a payer who tries again while the previous attempt is still processing waits for
its outcome on a "payment in progress" screen, in their language, instead of an English error. If
that attempt ends without charging, the checkout goes back to the payment method and shows the
refusal's
payer_message. The return after a failed payment carriesreference(the Wajub id) andtrxref(your reference). Hosted checkout - CLI 1.7.3 completes
payment.attempt_failedanddispute.deadline_approaching, and triggers every Mobile Money channel with its sandbox number. A login no longer moves your default profile: a first sandbox login becomes the default, a live one never does (wajub config use <profile>). With-d, a value with a leading zero or more than 15 digits, and a field namedphone, ending in_phone,account_numberorreference, stays text.wajub doctorrequires Node.js 22, andwajub open balanceopens the balances page.wajub listen --eventsno longer completespayment.checkout_page_opened,payment.redirected_to_callbackorlink.expired, which no endpoint receives. Triggers - Dashboard: a team can hold a second public key and edit or delete one. The Developer role manages providers, domains, the sandbox/live switch and orchestration (routing rules, channel policies, alerts), and can run the routing simulator and preview a rule's impact. Members who manage settings can require two-factor authentication. A member behind a blocked IP can still change their own password, two-factor and passkeys. The connection wizard leaves opt-in channels unticked. Team and security
- Dashboard: signing in with a passkey counts as the second factor. Close one of your other sessions
from Settings, Security. Filter the team audit log by member. A disabled API key is marked
Disabled, and restricted keys do not offer a
settingsscope. Approvingwajub loginor another device pairing requires the permission to manage API keys. - Dashboard: the sites allowed to embed your checkout have their own page, Settings → Checkout embedding, on every plan including Pay as you go, and the Developer role can change them. Once the list is set, only those sites can show your checkout. While it is empty, the Dashboard reminds you to declare them: do so before you take live payments.
- Dashboard: changing Team security, creating or editing an API key, creating, editing, deleting or promoting a webhook endpoint, inviting, changing or removing a member, leaving a team, changing the payout security settings and saving the embedding list ask you to confirm your identity; saving the embedding list is also recorded in the audit log. A webhook endpoint created without a description gets a validation error instead of a server error.
- Konsole: the API logs record each request's API version. Log explorer
SDKs Plugins
- Server SDKs retry the
409the API sends while a request with the sameIdempotency-Keyis still running, and return the first request's result: Node.js and C# 2.0.0, PHP 2.0.1, Ruby 1.3.0, Python, Go and Java 1.2.1. Go keeps oneIdempotency-Keyacross its retries. Idempotency - Server SDKs, same versions: security fixes in webhook signature verification. Update your server SDK.
- Node.js SDK 2.0.0: results are typed objects where 1.5.0 returned plain records,
WebhookEventcarries the type inevent, andexpires.inis typed.payments.initializeis gone: callcreate. Node.js SDK - C# SDK 2.0.0:
Webhooks.ConstructEventreturns plain values (string,longordouble,bool, dictionaries and lists) instead ofJsonElement. Code that readJsonElementmust switch to the plain values. C# SDK - Ruby SDK 1.3.0: the tax methods (reports, codes, registrations, jurisdictions, thresholds) and the Shield blocklist methods are in the gem, as in the other server SDKs. Ruby SDK
- React Native 1.4.1, Android 1.2.1 and Flutter 1.5.0:
401,404and an ended session (410) are not retryable, and a server error (5xx) is an API error. React Native and Android cancel the payment when the payer closes the sheet before submitting, as Flutter does, whoseshowWajubPaymentSheetnow callsonCancel. The React Native and Flutter sheets speak French. React Native opens the 3D Secure page on Android 11+ without a<queries>block, and@stripe/stripe-react-nativeis truly optional. Flutter'swatchStatus()follows the payment's Wajub id and keeps polling for the whole watch. Mobile SDKs - WordPress plugin 1.2.1: security fix for paid content and for LearnDash, LifterLMS and Tutor LMS course enrolment. Update to 1.2.1. The course buttons show only to signed-in visitors. Every integration reads the payer's return by the Wajub payment id, and an order made before 1.2.1 finds its payment by the reference the plugin sent. WordPress
- WordPress plugin 1.2.1: a partial refund started from WooCommerce is recorded once. MemberPress
sends the transaction receipt. The legacy webhook address answers
403to a delivery it refuses. - Documentation: a mobile
callbackmust be an https App Link or Universal Link. The Components pages documentisOpen(), field heights, the field types and properties, with examples that compile.
2026-10-11
NEW API
New dated API version: 2026-10-11, with one breaking change: a payment's status reads
refunded or partially_refunded once refunds have succeeded, and GET /payments?status= filters
on that reading, so a refunded payment is no longer listed under succeeded. Send
X-Wajub-Version: 2026-09-01 to keep reading succeeded.
What the payment's status says
Your account does not move. Every account that existed is now pinned to 2026-09-01, and a new one
is pinned to the version current at its creation. Until now an account carried no version and
followed the platform's, so a new version would have reached it the day it shipped. Webhook
payloads are still shaped for the latest version, whatever your account's.
Versioning
API Sync Shield Konsole
- A dispute you accept or close weighs less on the payer's reputation than one ruled against you.
The dispute now carries
metadata.resolutionset toaccepted. Risk scoring POST /beneficiaries/{id}/verifynow asks the operator for every account. Outside Sync Relay, checks made before this release did not reach the operator: check again any beneficiary whose name you rely on. On your own beneficiaries the answer is information: the call answers200withverification(verified,name_mismatchorlookup_unavailable) andregistered_name, the name the operator holds, and the beneficiary stays payable whatever it says. A sandbox key always getslookup_unavailable: sandbox has no operator. On a Relay account the call is still a gate and returns no name. A payout account added in the Dashboard can no longer be verified through this call. Checking the name on a wallet- Documentation: an
Idempotency-KeyonPOST /payments,/transfers,/transfers/batchand/refundsnever expires; only the stored response lasts 24 hours. A key reused for something new answers201with the old resource and does nothing. Idempotency - Coverage now marks as coming soon the rails no provider you can connect carries yet: Egypt, Algeria, Morocco, Tunisia, Sierra Leone, OPay and PalmPay in Nigeria, and crypto. Coverage
- Mobile SDKs: Flutter and React Native 1.4.0, Android 1.2.0. Cards through a provider that collects
them on its own page (Paystack, Flutterwave, PayPal, Kkiapay, FedaPay, CinetPay) and one-tap
wallets such as Djamo. A Mobile Money payment comes back as an action to approve,
push_approval. Mobile SDKs - Shield: Always allow customer on a review is open to every plan. An attempt Shield blocked is in your review queue, and allowing the customer lets their next payment through, unless they are on your blocklist or match a sanctions list. Shield, lists shows the customers you allowed and removes one. Blocking or refunding from a review, custom rules and the blocklist stay with Shield Advanced. Rules
- A recurring invoice now produces its copies: the next one is due one interval after
invoice_date, never in the past. Each copy is an ordinary draft, generated once per cycle, that keeps the whole-invoice discount; it still has to be sent and paid. The Dashboard's start and end dates are now applied. An invoice flagged recurring before this stays inert until its recurrence is saved again. Recurring invoices - Konsole: disabling or enabling a webhook endpoint from its edit form now takes effect.
- Konsole: a new endpoint's signing secret is shown once when you create it, Reveal works on the endpoint page, and Promote to Live creates the live endpoint and shows its secret once.
- Konsole: searching the API logs finds a call by its
X-Request-Id, in live as in sandbox. The JSON export carries the full records, and the exports of logs and events now apply the filters on screen. - Konsole: new filters for the endpoint (API logs), the category and delivery status (events) and the winning provider (routing log).
- Konsole: resending a delivery whose endpoint already accepted the event is refused and does not count against your Webhook replays per month. With no row ticked, bulk retry takes the failed deliveries of the last hour, 24 hours or 7 days. Webhook tester
- Konsole overview: Diagnose is offered on the authentication, latency and endpoint cards. "Unused API key" watches your API keys, and "Signing secret older than 6 months" counts from the last rotation.
- API logs redact fewer harmless fields and show a longer key prefix. Log explorer
POST /beneficiaries/{id}/verifyhas a ceiling of its own on every plan: 10 checks a minute and 200 a day per account in live, 10 a minute in sandbox. Past it the answer is429. Limits- Konsole no longer offers
payment.checkout_page_openedandpayment.redirected_to_callbackfor a webhook endpoint: they are never delivered. An endpoint that lists one keeps it, without effect. Webhook events - You can stop a recurring invoice whatever its status, paid included:
POST /invoices/{id}/stop-recurring, or Stop recurring in the invoice's actions menu. The copies already generated stay. The invoice object now carriesnext_invoice_date, when the next copy is due, andrecurring_parent, the recurring invoice a copy was generated from. Stopping a recurrence refund.updatedis now delivered, and an endpoint can subscribe to it. Who sends a live refund- A refund in a three-decimal currency such as TND keeps every decimal, so a full refund or an
accepted dispute goes through. In a currency with no subunit, such as XAF, a refund's fee share
and
payer_amountare whole numbers. Who bears the fee - With
X-Sync, a platform sees only the customers its connection created or used. A payment is created without any other customer, and theircus_id answers as unknown.DELETE /customers/{id}answers403: erasing a customer is the merchant's decision. Account types DELETE /customers/{id}now also removes the customer's details from invoices, a payment's addresses, the bodies of webhook deliveries (and their retries), the copies a Sync platform received, analytics and search. A repeated call finishes an erasure an earlier one left incomplete. Deleting a payerPOST /events/{id}/resenddelivers again to endpoints that have not accepted the event.- With
X-Sync, anIdempotency-Keybelongs to the connection, and accepting or closing a dispute is refused once the connection's payments are suspended. - A custom rule on
ip_countryreads only a verified payer address. If you run your own checkout - Retrying a payout batch with the same
Idempotency-Keyreturns the lines already sent, even when the hour has no place left. Limits POST /beneficiaries/{id}/verifymakes a beneficiary left unverified payable when the names match. Checking the name on a wallet- A payment sent to review because the payer's name is close to a sanctions listing appears in Shield, Reviews even when Shield is switched off. Risk scoring
- A generated recurring copy stays a draft until you send it, past its due date too; a copy that
read
overduewithout being sent is a draft again. A series turned off and on resumes after its latest copy, cancelling clearsnext_invoice_date, and a quote, an estimate or a generated copy cannot be made recurring (422). Duplicating a recurring invoice gives a plain draft. Recurring invoices - Konsole: Rotate on an endpoint's page replaces its signing secret, after your password, and shows the new one once. Webhook tester
- Webhook payloads are always shaped for the current API version, whatever the version of the call that raised them.
API Payments Invoices Sync Dashboard
POST /payments/{id}now returns what the payer's next step needs: a202that names a step inactioncarriesaction_url,otp_idandotp_masked_contact, thepin,phone,birthdayandaddressidentifiers, andclient_session.crypto.depositis a sandbox field. Whenactionis set- Wave (
ci.wave,sn.wave), Express Union (cm.eu) and M-Pesa (ke.mpesa,tz.mpesa,mz.mpesa) can be charged in live. Wave and Express Union take any number. M-Pesa takes a Safaricom number in Kenya and a Vodacom one in Tanzania and Mozambique, andmobileor a country aggregate sends such a number to M-Pesa. Every other channel takes only a number of its own operator. Letting Wajub find the operator - A payment created with
acceptcan be charged onmobileor on a country aggregate such ascm.mobile. The restriction is checked on the operator the number resolves to, and a number that resolves outside what the payment accepts is refused. Choosing what a payment accepts - A numbered page of a list now carries
next_cursorandhas_moreinmeta, next to the page counts. Send thatnext_cursorascursorand the walk continues right after the last item of the page. On every list that takes a cursor;GET /invoicesandGET /linkssorted withsort_bystay page-numbered. With the PHP SDK,getNextPage()andautoPagingIterator()follow that cursor from the second page on: same items, but themetaof those pages no longer hascurrent_page,last_pageandtotal. Pagination - A file attached to a dispute message with
POST /disputes/{uid}/messagesis now stored. Send message - An invoice with no
customer_emailand nocustomer_idcan now be paid: its page asks the payer for an e-mail, used for the payment and its confirmation. On that page, the e-mail you put on the invoice is always kept. Invoice quickstart - An online payment on an invoice adds to what you recorded with
mark-paid. Recording a payment you collected elsewhere - Sync: an accepted subaccount never reads
expired.expires_atis the invitation's date and is cleared when the merchant accepts, including on connections accepted before, and payments on an accepted connection go through. The Dashboard asks the merchant again before a pricing change. Onboarding - Sync: every
account.*event the API sends names its connection indata.account, theacc_…id.account.deauthorized,account.payment_activatedandaccount.payment_suspendedgain the key;data.idis not always the connection. Webhook events - API keys, Dashboard: a key's expiry can be set when it is created, changed or removed on a private or restricted key, and is shown next to the key. Editing anything else keeps it. The IP allow-list accepts IPv4 and IPv6 addresses and CIDR ranges in both families, on creation and on edit, and an IPv6 entry matches however it is written. Environments and keys
- Orchestration: the provider connection wizard pre-fills priority
0, the documented default, instead of50. Existing connections keep their priority. Providers - Docs playground: Test this endpoint works once you are signed in, in sandbox only. API reference
- Documentation:
callbackon a Sync connection is the page the merchant comes back to, not where events are sent. Events go to your webhook endpoints, andaccount.connectedneeds a subscription by name. Account capabilities - Documentation: the reference of
POST /beneficiariesrequireschannelandnameand has notype;POST /transfersdescribes the inlinebeneficiary(nameandchannelrequired, nobank_code) and saysreferenceis never checked for uniqueness, onlyIdempotency-Keyprotects a retry; a transfer'scompleteistrueonly onsucceeded; the Event object listsevent,created,livemode,pending_webhooks,api_versionandrequest. Transfers, Events - Documentation: the Tax quickstart now sets
inclusive: true, because it charges the calculated total. Withinclusive: false, a payment of 11 925 XAF recorded 2 296 XAF of tax instead of 1 925. If you followed the earlier version, setinclusivetotrue; past records are not recomputed. Tax quickstart - Documentation: the advice to keep five to ten calls in flight is replaced by a rate per minute
under each resource's ceiling for your plan (24 calls a minute on
/paymentson Pay as you go, where the ceiling is 30), with an example that waitsRetry-After. Performance - Documentation: in the Payment Links quickstart, extra fields are described with
nameandtype, notkey. The first example of the Components home page sendsemail; without a payer contact the API answers422. The Django marketplace cookbook creates the connection withwithdrawals, stores thepayout_phoneits payout example uses, and reads the connection indata.account. Payment Links quickstart, Django marketplace
Components SDKs CLI Plugins
- Wajub.js 1.6.0:
localegiven tomount(),open(),checkout()orcomponents()is now the language the checkout or field opens in. Hosted checkout - Wajub.js 1.6.0: a hosted checkout whose session cannot load calls
onLoadErrorwithsession_not_foundorsession_terminal_expired. It calledonError: move that handling toonLoadError. API reference - Wajub.js 1.6.0:
checkout.submit()presses the pay button of the method the payer is on.unmount()removes the hosted checkout and keeps the instance,mount()puts it back, inline and overlay. API reference @wajub/vueand@wajub/svelte1.6.0:useWajubOptional()returns a ref (Vue) or a store (Svelte) that follows the runtime. Readruntime.valueor$runtime.@wajub/svelteworks from the first render, with Svelte 4 and Svelte 5. Vue, Svelte- PHP SDK 2.0.1:
create(),retrieve()andupdate()ontransfers,beneficiaries,links,invoices,accountsandwebhookEndpointsno longer throwCannot instantiate abstract classafter the request was sent. If atransfers->create()failed this way on 2.0.0, the transfer was created: check before sending it again. PHP SDK - Python SDK 1.2.1: the package on PyPI contains the module again. 1.1.0 and 1.1.1 installed an
empty wheel. Install with
pip install "wajub>=1.2.1". Python SDK - Ruby SDK 1.3.0:
customers.delete_tax_idcallsDELETE /customers/{id}/tax_ids/{tax_id}. Ruby SDK payments.initialize, an alias ofcreate, is no longer in the Node.js and C# SDKs (2.0.0), nor in Python, Go and Java (1.2.1) and Ruby (1.3.0). PHP keeps it. Callcreate. SDKs- SDK examples on the payout, balance, beneficiary, payment link, invoice and dispute pages now match each SDK's real signatures. Dispute evidence with a file is shown as a multipart request.
- React Native SDK 1.4.1:
watchStatus()reports when realtime is on, and polling runs for the whole watch. Android SDK 1.2.1:watchStatus()opens the realtime socket and polls next to it. Mobile SDKs - CLI 1.7.2:
wajub trigger --channelpays with a sandbox number of that channel's operator,--channel ci.orangeincluded. Triggers - WordPress plugin 1.2.1: a customer back from the payment page finds their order in the state of
its payment (WooCommerce, GiveWP); refunds from WordPress are accepted; a Dashboard refund reaches
WooCommerce;
[wajub_pay_content]unlocks. WordPress
2026-10-10
API Orchestration Sync Tax Shield Webhooks Components CLI
- Sync and Tax calls need the product in your plan or add-on: without it,
/accounts, any call carryingX-Syncand/taxanswer403witherror_code: feature_not_in_plan. Sync · Tax - A live webhook endpoint must use
https://: creating one onhttp://, or pointing one at anhttp://URL, answers422. Sandbox endpoints still accepthttp://. Live endpoints already onhttp://keep their deliveries, and Konsole flags them. Webhooks customer_idonPOSTandPUT /invoicestakes the customer'scus_id. InvoicesPUT /accounts/{id}answers409witherror_code: merchant_not_verifiedwhen it setspayment_status: activeon a merchant whose verification is not approved. Onboarding- With
X-Sync, a platform reaches only what the connection brought. Disputes are those of its payments; links, invoices and beneficiaries are those it created, and a payout goes only to one of those beneficiaries;GET /eventslists the events about all of these, andPOST /events/{id}/resendsends to your own endpoints. Anything else answers404./balance,/webhooks,/shield,/taxand/listenanswer403withX-Sync. A payment on a connection's link or invoice carries the connection, and is refused once the connection may no longer collect.write,settings,subscriptionsandanalyticsare no longer capabilities: asking for one answers422. Account types DELETE /customers/{id}anonymises and deletes the customer; if a part fails, it answers500and changes nothing.customer.deletedcarries the anonymised record and is no longer followed bycustomer.updated. The customer's details are removed from the events already recorded, a repeated call answers200instead of404, and the archived copy is deleted after 120 days. Deleting a payer- The hourly payout velocity counts accepted payouts only: a refused one, for want of funds or by the ceiling itself, takes none of the six places, so retrying it does not extend the wait. A batch takes one place whatever its number of lines; each line still adds its amount to the hourly amount. Limits
- On a charge made with a secret key, have the payer's IP scored: call
Wajub.signals()(Wajub.js 1.6.0) in the payer's browser and pass the result asdata._client. Itstokenvouches for the payer's address. If you run your own checkout - A payer whose name is close to a sanctions listing, without being it, is now sent to review
whatever the score. You can choose to refuse it instead, with
sanctions_near_match_actiononPUT /shield/settingsor in Shield, Risk controls. An exact match is still always refused. Risk scoring - A payment now moves on to your next provider when the first one answers that the path is down: the operator timed out or is unavailable, your credentials are refused, or its float is exhausted. A refusal about the payer still stops there. What stops the ladder
- A live refund says who sends it:
execution_mode(automatic,merchantormanual) and, when it is not sent automatically,manual_reasonwith a stablecodeand amessage.refund.updatedis sent when either changes. A refund that is stillprocessingcounts against what is left to refund. Who sends a live refund - You choose who bears a payment's Wajub fee when you refund it:
fees_beareronPOST /refundsand in the Dashboard's refund form, with a default in Settings, Refunds.merchantgives the customer back everything they paid for the refunded share,customerleaves the fee share with them. The refund carriesfee_amountandpayer_amount, and your balance is debitedpayer_amount. Nothing changes unless you set it: the fee stays with whoever paid it. Who bears the fee - A payment created with
bearerset tocustomercosts you no fee: the fee is taken once, from the payer, and nofee.chargedis sent for that payment. Sandbox does the same. Who pays it - Accepting or closing a dispute through the API now refunds the payer:
POST /disputes/{id}/acceptand/closecreate a refund of the disputed amount with the reasoncustomer_complaint. That refund does not debit you a second time. A chargeback, which the payer's bank has already paid back, is not refunded on top, from the API or the Dashboard.dispute_settlementis a reserved key in a refund'smetadata. Conceding - Wajub Components:
fetchSession()works from any merchant site. Sessions js.wajub.comnow sendsAccess-Control-Allow-Origin: *, so importingwajub.mjsas a module works. Wajub.js- CLI 1.7.1:
wajub logoutalso removes the key fromconfig.json.bak. Delete anyconfig.json.bakan older version left behind. Log out
2026-10-09
NEW Payments Components
deferred: trueonPOST /paymentsprepares a payment for payment fields shown before the order exists: no transaction, nopayment.createdand no line in your payments untilPOST /payments/{id}/activatecreates it with the final total and the customer. Before the order existsPATCH /payments/{id}changes a prepared payment's amount and currency while the checkout is open, without remounting the fields. Update a prepared payment- Wajub Components:
variant: 'fields'fits a component's inputs into your own payment method box, andfields.countryandfields.operator: 'detect'drop the country and operator lists from mobile money. AlsoonBeforeSubmit,fields.customer: 'external', and thebankTransferandcryptocomponents. The runtime served fromjs.wajub.comreads them,@wajub/js1.5.0 adds their types, and@wajub/vueand@wajub/svelte1.5.1 passvariant. Inside your own payment method box
Orchestration
- The router applies the most specific instruction first: the connections your matching routing
rules name, each followed by the rule's fallback, then the channel's policy, or the connection
priorityon a channel without one. Orchestration - Routing rules now apply their weight and their advanced conditions: time windows, customer
segments, metadata filters and frequency caps. A deleted rule never routes. A metadata filter
intakes values separated by commas, andeqandincompare by text, so100typed in the Dashboard matches a numeric value. Routing rules - A connection's fixed fee is converted from your base currency into the payment's currency before the router compares costs.
- A channel policy's cascade order, objective and guardrails order the connections no rule placed. A guardrail moves a connection to the back, it never refuses a payment.
- The routing simulator on the Routing Rules page runs the real router in a dry run.
- A card entered in a provider's own fields is only charged by that provider, and never failed over. A Stripe decline is final and carries Stripe's decline code.
- A connection without credentials is skipped: the next one is tried. A missing or rejected key is flagged on the connection, and your team is emailed about it. Waterfall & fallback
2026-10-03
CLI
- CLI 1.7.0:
wajub completionreplaceswajub autocomplete. Runwajub completion installonce;wajub autocompleteremains as an alias, and a shell set up by it keeps completing after the upgrade. Completion now covers values (statuses, event types, API paths, profiles), fish and the bash macOS ships. CLI - CLI 1.6.1: the first published release with listener sessions (1.6.0 never reached npm).
2026-10-02
CLI
- CLI 1.6.0:
wajub listenstreams through listener sessions, and your handler receives each delivery exactly as production sends it. CLI 1.5.0 and earlier can no longer listen: theirwajub listenstops and asks you to update (npm install -g @wajub/cli@latest). Every other command of older versions keeps working. Listen - CLI 1.5.0: the Agent Skills are copied into
~/.agents/skillsand~/.claude/skillson the first command after installing or upgrading, withoutwajub login. Opt out withWAJUB_NO_SKILLS=1.
2026-09-19
API Payments
- The rate limit is now your plan's ceiling per minute, shared by all your keys, with a smaller share for payments, payouts and a few other routes, and no ceiling on Enterprise except payout writes (creating beneficiaries, transfers and batches), capped at 20 a minute on every plan. Rate limits
- A chargeback opened at a provider now reaches you as a dispute, with its deadline in
due_date.
2026-09-17
NEW Payments Transfers Sync
- In live, raw card data sent to
POST /payments/{id}is refused. - Receivables:
POST /beneficiaries/{id}/receivablesrecords what a paid-out seller owes back, and a batch payout line can take it back first. New eventsbeneficiary.receivable_recordedandbeneficiary.receivable_recovered. - Payouts to a Wajub account.
- Sync:
account.change_requestedwhenPUT /accounts/{id}proposes new terms, andaccount.commission_receivable_recordedandaccount.commission_receivable_collectedfor a commission that could not be taken at the source.
2026-09-16
NEW Transfers Sync
POST /transfers/batch: up to 500 payouts in one call, with a requiredIdempotency-Key.- Sync accounts get a mode, Lite, Direct or Relay, and
POST /accounts/{id}/payoutspays a Lite merchant, in live only. - The onboarding webhooks sent to a Sync platform are now named
account.onboarding_started,account.onboarding_email_verified,account.onboarding_account_selected,account.onboarding_kyc_submittedandaccount.connected, and can be subscribed to. Event catalogue
2026-09-15
Webhooks API Transfers
- Webhook retries: every failed delivery except a
410is retried with a growing wait, for up to 72 hours. It used to be five attempts over about sixteen minutes, and a4xxwas never retried. Retries - Idempotency: the stored response is replayed with
Idempotent-Replayed: true, a second request arriving while the first still runs gets a409, and the same key with another body gets a422. Idempotency - Bank transfer payouts.
SDKs
- PHP SDK 2.0.0, with breaking changes: it requires PHP 8.4;
update()anddelete()are removed fromrefundsandtransfers, which the API never had;tax->reports()returns the report object,shield->addToBlocklist()the updated blocklist, andlisten->config()andlisten->auth()unwrap their envelope. Cursor pagination is followed bygetNextPage()andautoPagingIterator(). PHP SDK
2026-09-11
Payments Transfers CLI
- Djamo wallets, for payments and for beneficiaries.
DELETE /keys/currentrevokes the keywajub logincreated.wajub logoutcalls it.
2026-09-07
Transfers
- Added
sn.wavechannel for transfers in Senegal.
2026-09-01
NEW API
New dated API version: 2026-09-01, with one breaking change: ledger_balance_before and
ledger_balance_after are removed from the transfer object. Send X-Wajub-Version: 2026-08-01
to keep them. Versioning
The API accepted this version from 10 September: before that, X-Wajub-Version: 2026-09-01
answered 400.
2026-08-30
Payments
DELETE /customers/{id}anonymises the customer: the name is replaced, and the email, phone and date of birth are erased.customer.deletedcarries the anonymised record.
2026-08-12
Payments
- A charge the provider declines answers
402with anerror_code,insufficient_fundsfor instance, and the payment goes back topending, ready to be charged again.
2026-08-11
Payments
- A payment method is saved only with the payer's consent, sent as
data.save_payment_method.
2026-08-10
Webhooks
payment.processingis emitted when a payment is handed to the operator.
2026-08-07
Webhooks
fee.chargedis emitted when Wajub debits a fee. Fee debits are left out ofbalance.updated.
2026-08-01
NEW API
New dated API version: 2026-08-01, with one breaking change: complete replaces
is_complete on the transfer object. Send X-Wajub-Version: 2025-01-01 to keep reading
is_complete. Versioning
2026-07-24
Orchestration
- A provider connection's
prioritynow comes first: a lower-priority connection is never tried before a higher one, and cost and rules only decide between connections of the same priority.
2026-05-23
BETA Shield
Shield enters public beta: a risk score from 0 to 100 built on signal families (IP, velocity,
card, device, blocklist, sanctions), configurable review_threshold/block_threshold, and a blocklist.
Live only. See Shield →
2026-05-20
NEW Konsole
Konsole launch, the developer cockpit: Overview, API Logs, and Webhooks. Discover Konsole →
2026-04-03
BETA Tax
Tax in beta: automatic VAT calculation for CM, CI, SN, NG, GH, and KE, in inclusive and exclusive modes. See Tax →
2026-01-09
NEW Link
Link launch, code-free payment links with brand customization and conversion tracking. See Link →