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-23
Sync Webhooks
- New event
account.onboarding_declined: the merchant declined the connection in the hosted onboarding. Nothing is connected and the invitation link keeps working;reasoncarries the merchant's words when they gave one. Like the other events of the merchant's own steps, it reaches only an endpoint subscribed to its name. Accounts, for Sync platforms
2026-10-17
BREAKING Sync
A change on every API version at once: it is not tied to a dated version, and X-Wajub-Version
does not keep the earlier behaviour. It concerns the Sync deliveries that carry X-Sync-Event,
those of the merchant's own steps, and the return to your page.
- Breaking for a platform that verifies
X-Sync-Signature: those deliveries no longer carry it. VerifyX-Sync-Signature-V1withX-Sync-Timestamp, asconstructEventdoes in the SDKs. Signature verification - Breaking for a platform that reads
sync_uid: those deliveries name the connection only inaccount, theacc_…id, and carry none of Wajub's internal ids of the connection, the platform or the merchant.account.onboarding_startedno longer carries the address the merchant opened the link from. Sync lifecycle deliveries - Breaking for a platform that reads
sync_idon its return page: the merchant comes back withaccount,stateandstatusonly. Back on your page - Only a
2xxis a delivery: a redirect is not followed and counts as a failure. Each attempt goes to the endpoint as it is then, signed with its current secret, and an endpoint disabled or deleted in the meantime is sent nothing more.
API Webhooks Konsole
- Resending webhooks by hand is no longer counted against a monthly allowance, on any plan. The same
event goes again to the same endpoint at most once every 5 minutes, from Konsole or from
POST /events/{id}/resend. The call skips an endpoint inside that delay, and answers429witherror_code: event_resend_cooldownand aRetry-Afterheader when every endpoint that has not accepted the event is. A 429 that is not a throttle - Konsole: Resend inside that delay says how many minutes are left, and Retry failed deliveries skips those deliveries and counts them in its message. Webhook tester
Link Orchestration
- A payment link's own webhook counts as delivered only on a
2xx: any other answer, a redirect included, is logged as failed and retried, and a redirect is never followed. Its test send says only whether the endpoint accepted it. In the Dashboard - An alert rule's Slack or webhook target must be an
httpsURL that does not point to a private or internal address: Wajub sends nothing to any other, and follows no redirect from it. Alerts
BREAKING API
A change on every API version at once: it is not tied to a dated version, and X-Wajub-Version
does not keep the earlier behaviour.
- Breaking for an integration that builds or edits a
cursor: a list takes only anext_cursororprev_cursorexactly as a response returned it. Any other value, a cursor made by hand or altered included, is a422oncursor. A cursor returned before this change is still read until 2026-11-13. Cursor pagination - Breaking for an integration that sends
GET /balancea currency Wajub cannot convert to:currencytakes your account's currency, or a currency Wajub has an exchange rate for, in upper or lower case. Any other code is a422oncurrency. Balance - Breaking for a platform that tells
403from404withX-Sync: a connection that belongs to another platform answers404 Sync account not found., as an id that does not exist does. Keys and access
API
- A restricted key (
rk.orrk_test.) used from a browser sets off the same security alert as a secret key: the account's owner and admins are emailed. For now the request still goes through; Wajub will refuse it with the same403later, so move those calls to your server. Private keys are blocked in the browser
2026-10-16
BREAKING API
API version 2026-10-12, the version of new accounts, also brings these changes. Earlier
versions keep the previous behaviour, and webhooks follow the current version, 2026-10-11.
- A payment no longer has
theming. Earlier versions still read the key,nullon a payment created from now on. POST /webhooksandPUT /webhooks/{id}check each type ofeventsagainst the event catalog: a type that does not exist, a misspelt one or a prefix such aspayment.*answers422onevents.N.*still takes every type, and an endpoint keeps the types it already has. Webhook endpoints- A Shield blocklist entry no longer carries
team_id. Shield - The
itemsof a payment made on an invoice's page end with the invoice-wide discount, when there is one: an item withkind: discount,name: Discount, a negativetotal, andquantityandunit_priceatnull. The Payment object
API Payments
DELETE /payments/{id}answers409when the payment cannot be cancelled now:error_code: payment_in_progresswhile a charge is under way, the payer approving it on their phone or paying on the provider's page, andpayment_partly_paidon apartialpayment or one with instalments paid, to close withPOST /payments/{id}/close. Aprocessingpayment with nothing under way can be cancelled, and so can one past itsexpires_atthat has not expired yet. Cancelling a payment- Of two charges sent at once on one payment, the second answers
409 payment_in_progress. A payment whose charge is still under way whenexpires_atpasses waits for that charge's outcome before it can expire, and in live a charge on a payment past itsexpires_atanswers400. Expiry - Some operators, such as Orange Money in Burkina Faso, take a payment only with a code the payer
generates first. The charge then answers
202withaction: payer_otpand, inpayer_otp, how to get the code: charge again with it indata.otp. After three refused codes, the charge answers402 otp_attempts_exceeded. In sandbox,bf.orangeasks for the code and accepts123456. A code the payer generates - The payment object returns
clientto a secret or restricted key: where the payer paid from, the hosted checkout, Wajub.js, a mobile SDK or another app, as it reports itself, with its version and platform when they are known. The Payment object POST /paymentsno longer keepstheming, on every version: the field is ignored, never refused.otp_masked_contactholds only the masked contact, such as080******78, and is empty when the provider names none.GET /payments/optionsgives each channel and operatorlogo_png, its logo as a PNG. Previewing the methods- On a charge, a Mobile Money number may start with
00instead of+. The 0 that is part of a number in Côte d'Ivoire, Benin, Congo and Burkina Faso is kept, and Benin's and Gabon's renumberings are read in their new form. Phone number formats - Sandbox: Benin's ten-digit numbers (
+2290190…,+2290191…,+2290197…) and Gabon's current blocks (+24174…,+24177…,+24162…,+24165…,+24166…) have test numbers, and00may stand for+. Test scenarios
API
POST /accounts,POST /tax/registrations,POST /customers/{id}/tax_idsandPOST /disputes/{id}/messageshonourIdempotency-Key: a repeated call answers what the first one did, for 24 hours. Idempotency- A path that encodes a slash or a backslash,
%2For%5C, answers404. Error shape - A dispute's
customeris the customer'scus_id, even once that customer is deleted, and itstransactionthe payment'strx_id. Either is left out when there is none. - On the versions before
2026-10-12, a channel ofGET /channelsandGET /providersreads the currency it pays in, ornullfor a channel that is not paid in one currency, such as a card or a crypto asset, instead ofXAFfor every channel. Coverage
API Link
single_use_link: truecloses a link after its first successful payment and lets one payer pay through it at a time: another payer is told a payment is in progress, and the link opens again if that payment does not go through. A refund does not reopen it. Should a second payment still succeed, your team's owner, administrators and finance members get an e-mail naming the payment to refund. A link that already hadsingle_use_linknow closes after its first payment, except adigitalproduct's, whose setting becomessingle_use_access. How a link closessingle_use_access: true, for adigitalproduct, lets each buyer download the file once; the link stays open to the next buyer. The buyer downloads through a link bound to their payment, never the file's own address, and a full refund stops the download.file_urlonPOST /linkstakes an address of your own: a file uploaded in the Dashboard answers422. Digital products- A service's meeting link reaches the payer in the confirmation e-mail of their payment, with what to prepare, and is no longer on the page before payment.
link.expiredis sent once, within a day after a link'sexpiry_datepasses, unless the link is archived by then, with the link indata, to the endpoints that subscribe to it, andGET /events?type=link.expiredlists it. Event catalogue
API Invoices
- Line discounts come off an invoice's
subtotal,totalandamount_due, and a discount can no longer take a line below zero:422onitems.N.discount_value. Open invoices with a discounted line are brought in line, which can lower theirtotalandamount_due, never raise those of an invoice already sent, and turns one already paid up to its new totalpaid, withinvoice.updatedandinvoice.paid. How a line is computed - The
itemsof a payment made on an invoice's page give each line itstotal, net of its discount and tax included, on every version. The Payment object global_discount_typeandglobal_discount_valuetake a discount off the whole invoice, which returns them withhas_global_discount. The receipt and the payment confirmation list it after the lines. An invoice-wide discount- Quotes and estimates made with
POST /invoicesare read, corrected, cancelled and deleted by theirid, andGET /invoices?document_type=quoteorestimatelists them./sendand/mark-paidanswer400on them. Quotes and estimates - Your customers are reminded of unpaid invoices by e-mail before and after the due date, invoices sent through the API included, unless you turn reminders off in Settings → Invoice → Automatic Reminders, where you also pick the days. An invoice already sent from the Dashboard keeps the reminders already scheduled for it; your days take over for the days that follow. Reminders
- Invoice and reminder e-mails are written in the invoice's
language, their footer included.
SDKs
- The next releases of the Flutter (1.6.0), React Native (1.5.0) and Android (1.3.0) SDKs guide the
payer through a code they generate in their operator's menu, in a new state,
PAYER_OTP_REQUIRED(CheckoutState.payerOtpRequiredin Dart). Awhenor aswitchthat lists every state needs a branch for it. Every amount the sheet shows is what the payer pays, the Wajub fee included when they bear it, for the method or operator they pick. States
2026-10-15
NEW Plugins
- PrestaShop module 1.0.1, for PrestaShop 8.1 to 9: Mobile Money and Card as payment options of their own, with Wajub's payment form inside the checkout (Classic and Hummingbird), and Other payment methods on Wajub's payment page, each shown only when your account offers it for the cart. The order is created when the customer places it and settled by the webhook, instalment payments included; a payment short of the total counts, and the customer pays the rest. An order PrestaShop splits into several packages is paid, refunded and cancelled as one. PrestaShop
- PrestaShop module 1.0.1: Connect with Wajub gets the shop its key from the Dashboard, with
the client type
prestashop; the back office and the shop's e-mail address are told from 14 days before a key it created expires. Refunds both ways, from the order page and from the Dashboard; a refund Wajub or the module refuses makes no credit slip. One connection per shop of a multistore, and the customer's data for PrestaShop's GDPR module. Connect your app to Wajub - PrestaShop module 1.0.1: alerts in the back office for an environment chosen without its key, a key that expires, a shop open to the public still in Sandbox, Wajub missing from the checkout, and a scheduled task that does not run; the settings warn a shop not served over HTTPS in Live. Wajub · Sandbox shows in the back office's header while the shop takes test payments. Uninstalling keeps the record of the orders' Wajub payments, refunds and notes. Back-office alerts
2026-10-14
BREAKING API Transfers
A privacy change on every API version at once: it is not tied to a dated version, and
X-Wajub-Version does not keep the earlier behaviour.
- Breaking for an integration that shows
registered_nameafter aname_mismatch:POST /beneficiaries/{id}/verifyreturns only the initials of the name the operator holds when it does not match the one you declared,P*** N***. A match still returns the full name. Checking the name on a wallet
BREAKING API Payments
A security change on every API version at once: it is not tied to a dated version, and
X-Wajub-Version does not keep the earlier behaviour.
- Breaking for an integration that reads a payment's customer with a publishable key (
pk.):POST /paymentsreturns incustomer,shipping_addressandbilling_addressonly what the request sent, never the customer the account already has with that email or phone.GET /payments/{id}, andPOST /payments/{id},/splitsand/confirm, return nocustomer,shipping_addressorbilling_address, andpayment_methodholds onlychannel. Read them from your server, with a secret or restricted key. Authentication - Breaking for an integration that reads the customer
POST /customersreturns with a publishable key: when the account already has a customer with that email or phone, the answer, still200, holds itsidand the fields the request sent. Read the stored customer from your server, with a secret or restricted key; those keys are unchanged. Authentication - Breaking for an integration that reuses an
Idempotency-KeyonPOST /paymentswith another body: the whole body is compared,expires,customer,metadata,bearer,addressandshippingincluded, and a change answers422. OnPOST /payments, a key a publishable key used answers422to a secret or restricted key, and the other way round. Idempotency
API Payments
expires.insent empty, blank ornullgivesPOST /paymentsits default expiry, 24 hours. OnPATCH /payments/{id}, it leaves the expiry as it was. Update a prepared payment- OPay (
ng.opay) and PalmPay (ng.palmpay) take a number from any network, as Wave, Express Union, Djamo and Capitec Pay do. Direct API
CLI
- CLI 1.7.4: a
406 Private Key Required, the answer to a publishable key (pk.) on a route that takes a secret one, such aswajub payments listorwajub customers retrieve, says the request needs a secret (sk.) or restricted (rk.) key, names where the publishable key came from (WAJUB_API_KEY,--api-keyor the profile) and says how to put a secret key there,wajub loginfor a profile.
Dashboard
- Changing a customer's details or status now needs a recent security verification, as revealing a key does: editing, blocking, unblocking, activating, deactivating or deleting a customer, one at a time or, editing aside, in bulk, and deleting one for good.
- Deleting customers in bulk now anonymises them, as deleting one does.
- Creating or editing a live customer with an email no longer fails with an error.
- A beneficiary's type is shown read-only on its edit page: it is set when the beneficiary is created, and the page no longer offers a change it did not make.
Components
- Wajub.js 1.8.1:
@wajub/angularis new, for Angular 17 and later, installed with@wajub/js1.8.0 or later beside it.provideWajub()andWajubServiceload Wajub.js. Standalone components render the hosted checkout,<wajub-checkout-embed>, and each field group,<wajub-card>to<wajub-crypto>, or any of them with<wajub-component>, andinjectConfirmPayment()submits a field. Options are inputs, callbacks are outputs, andonBeforeSubmitis thebeforeSubmitinput. The package works with zone.js and zoneless, and with Angular SSR.@wajub/js,@wajub/react,@wajub/vueand@wajub/sveltemove to 1.8.1 with no other change. Angular - Wajub.js 1.8.0:
loader: 'skeleton'keeps your page still while a payment field loads: a placeholder drawn in your fields' style holds the form's height until the form is ready.componentReserveHeight(type, variant, fields), onwajuband as a synchronous export of@wajub/js, gives that height.componentEstimatedHeight(type, variant, fields), from@wajub/js, gives an estimate that is the same on the server and at hydration.readyTimeoutreports a field that is not ready in time toonLoadErrorandloaderror, withready_timeout. Loading without a layout shift - Wajub.js 1.8.0:
appearance.inherit,trueor{ input, label, button, background }, takes the look of your page's fields, label, button and background, under the keys you set, in payment fields,mount()andopen().wajub.appearanceFrom(), also on theWajub()client and in@wajub/js, returns what it reads,{}from@wajub/jswhen the page runs an older Wajub.js. Taking your page's look - Wajub.js 1.8.0:
onStateChange,onResizeandonRedirectatcreate(), for thestatechange,resizeandredirectevents, andon()andoff()type each event's payload throughComponentEventMap.confirmPayment({ timeout })no longer counts the time the payment waits on the payer, such asAPPROVAL_REQUIRED,OTP_REQUIREDor3DS_REQUIRED, nor the time the payer spends on the provider's page. A field taken off the page withoutunmount()rejects it withcomponent_unmounted, and a field mounted again starts fromINITIATED. Locking your checkout while the payment runs - Wajub.js 1.8.0:
update({ appearance }), on a field,mount()oropen(), changes the keys you give and keeps the others. A key given asundefinedornullis cleared, andappearance: undefinedclears it all, back to your Dashboard branding. Onmount()andopen(), a newthemepreset replaces the previous one and what it brought.update({ layout })lays out an embedded checkout's methods again, inline and in the overlay. Changing it after mount @wajub/react,@wajub/vueand@wajub/svelte1.8.0, which need@wajub/js1.8.0: every option above is a prop, andBankTransferComponentandCryptoComponentjoin the field components.CheckoutEmbedapplies a change ofappearance,theme,localeorlayoutin place, and loads again only for a newsessionIdorembedOrigin.useConfirmPaymentreads the session when you confirm, from a ref or a getter in Vue and a store or a getter in Svelte, and asessionIdpassed toconfirm()wins. The Svelte field components take every handler as a prop, and type every prop. Mobile Money and Card in your own checkout- Changes for the React, Vue and Svelte field components in 1.8.0:
loaderdefaults to'skeleton', so passloader="none"to keep the frame as it comes.minHeighthas no fixed default: the component holds the room the form needs, sominHeight={0}is no longer needed withvariant="fields", and aminHeightyou pass is kept. A newvariant,fields,loader,readyTimeout,collectAddress,addressModeorcollectNamemounts a new field in the same container. Theappearanceprop is sent whole: a key you remove from it is cleared. Changing a handler never reloads the form. Through a framework - A payment field whose session already ended shows its notice, then sends
readywhen the session was paid, elseloaderrorwithsession_terminal_expired,session_terminal_cancelledorsession_terminal_failed: start a new payment. A field that cannot tell which page it is in sendsloaderrorwithcomponent_origin_missing. When the form does not come up - Wajub.js 1.7.2: with
variant: 'fields', a component takes the height of its inputs; with the React, Vue and Svelte field components, passminHeight0.rulesvalues the checkout refuses are dropped. Inside your own payment method box update({ appearance })on an embedded checkout lasts through the payer's next steps. The pagecheckout()sends the payer to takes your appearance from its first render. AcolorSchemeyou give applies from the first paint on every page, and hides the checkout's light and dark button. Thecolorand the font of a.Inputrule reach the typed text, card fields included, and thecolorof a.Labelrule reaches the card holder's label. A Google Font the checkout loads reaches the card fields, and withoutfontSizeBasethe fields are 16px on a phone or a tablet, so iOS does not zoom in on focus. OPay and PalmPay take a number from any network, as Wave, Express Union and Djamo do. Appearance- Wajub.js 1.7.1:
appearance.themeset tolight,dark,systemorautois read ascolorScheme,systemmeaningauto, when you give nocolorScheme. The presetsstripe,night,flatandnoneare unchanged. Presets - A
colorSchemeyou give wins over the payer's device and the choice they saved, in a payment field and in the embedded checkout.variables.fontFamily, which loads a Google Font the checkout knows,fontSizeBase, which sizes the fields, at least 16px on a phone or a tablet, with labels and hints a step smaller,colorText,colorTextSecondary,colorTextPlaceholderandborderRadiusnow style the fields.inputBackgroundColorandinputBorderColorstyle the fields alone.rulesfor.Inputand.Labelreach the fields, and the check onrulesvalues applies on every path,update({ appearance })included. Rules readyfires once the component's form is on screen, or at once when it has no form to show. In the Mobile Money form, each operator of a channel that carries several, such as MTN and Orange in Cameroon, is a choice of its own with its logo, and withfields.operator: 'detect'the list shows when the number does not tell the operator. A wallet held on any network, such as Wave, Express Union or Djamo, takes any number. Inside your own payment method box
API Sync
- A subaccount names the merchant who claimed it
connected_merchant, on every API version.slave, the same object, is deprecated and gone from API version 2026-10-12, which also drops theidofmasterandconnected_merchantand a channel'suid, a repeat of itsid, and identifies a provider ofGET /providersbyprv.and its slug. Versioning
2026-10-13
BREAKING API Payments
A security change on every API version at once: it is not tied to a dated version, and
X-Wajub-Version does not keep the earlier behaviour.
- Breaking for an integration that calls these routes with a publishable key (
pk.): they now answer406 Private Key Required.GET /payments,DELETE /payments/{id},POST /payments/{id}/closeandGET /payments/{id}/refunds;GET /customers,GET,PUTandDELETE /customers/{id}, and itsblock,unblock,activateanddeactivate; every/linksand/invoicesroute. Make those calls from your server, with a secret or restricted key. A publishable key still creates, charges, confirms and reads a payment by its id, readsGET /payments/options, creates a customer withPOST /customers, and reads the reference data. Authentication - Breaking for a restricted key that holds only
payment.*scopes:POST /payments/{id}/closewithoutcome: refundalso takesrefund.write, asPOST /refundsdoes, andGET /payments/{id}/refundsalso takesrefund.read. Add the refund scope the key needs. Restricted key scopes - Breaking for a platform whose connection holds
paymentswithoutrefunds: withX-Sync,GET /payments/{id}/refundsandPOST /payments/{id}/closewithoutcome: refundalso take the connection'srefundscapability, as/refundsdoes: a connection withpaymentsalone now gets403. Account capabilities
API Payments
POST /payments/{id}/activatetakesreferenceanddescription, which replace the ones given when the payment was prepared, before your order existed. Left out, the prepared ones stay. Activate a prepared paymentPATCH /payments/{id}checks the new amount asPOST /paymentsdoes: outside the currency's limits it answers422onamount, and whenacceptleaves no way to pay it,422onaccept. It takesexpires.in, from 5 to 43 200 minutes from now, and the payment keeps that expiry once activated. Update a prepared payment
Dashboard CLI Plugins
- Approving
wajub loginor an app's connection, such as the WordPress plugin's Connect with Wajub, takes the owner, an Administrator or a Developer (a Sandbox User, for a sandbox key only), and a recent security verification code. The page always starts on Sandbox, whatever the app asked for, and the approver picks the environment. Install and authenticate - The approver chooses how long an app's key lasts: 90 days, the default, or no expiry. A
wajub loginkey always lasts 90 days. The owner and every Administrator get an email for each key a pairing creates. - Settings → Developers → API keys shows when each key stops working: Expires … or Expired …. A key with no expiry shows no date. Environments & API keys
- Transferring the team, confirming a payout, and revealing an API key or copying its
.envlines ask for a security verification code, from your authenticator app or sent by SMS or email, like rotating or deleting a key. Team and security
Components
- Wajub.js 1.7.0:
frameTitle, passed to a component'screate()orupdate(), names its iframe for screen readers in your page's language. - Wajub.js 1.7.0: when the payment continues on the provider's own page, the component emits
redirectwith{ url, method }and Wajub.js takes your page there. Back from that page, the payer lands on the session's Wajub page, in the component's language, which shows the outcome and then sends the payer to the payment'scallbackwithstatus,referenceandtrxref, or keeps the outcome on screen when there is nocallback. A page on an earlier Wajub.js keeps the earlier behaviour. When the payment continues on the provider's page - A submission you ask for with
submit()orconfirmPayment()that a component refuses, such as a Mobile Money number empty or not valid, a card left incomplete or rejected, card fields still loading, a bank transfer with no IBAN or account number or a crypto payment with no asset, emitserrorwithsubmission_failedand a message for the payer, andconfirmPayment()rejects with it. Enter pressed in a form that is not ready shows the reason in the component only. A submission while the payment runs does nothing, so nothing is charged twice. The OTP, PIN, phone approval, USSD and 3-D Secure screens give their height toresize, and a card window the browser blocked afteronBeforeSubmitleaves a Continue on … button in the component. Submitting
Plugins
- WordPress plugin 1.4.0: two new WooCommerce payment methods, Mobile Money and Card, each with Wajub's payment form inside your checkout, classic and Blocks. Both stay off until you turn them on, so an existing store sees no change. WordPress
- WordPress plugin 1.4.0: Connect with Wajub, in the setup assistant and in Settings, gets the site its key once you approve it in the Dashboard, with nothing to copy; pasting keys stays available. The key's expiry shows in the admin, and the site's administration address gets an email from 14 days before it.
- WordPress plugin 1.4.0, behaviour change: each environment pays with its own key only. A site in Sandbox with only a live key no longer takes real payments with it, and takes none until you add a sandbox key or switch to Live. On update, a production site in that state switches to Live once and says so; a local, development or staging copy stays in Sandbox.
- WordPress plugin 1.4.0: a WooCommerce customer pays only what is left. A payment short of the order's total, such as an instalment payment closed early, counts toward it, and the customer pays the rest from the order's payment page. Refunds from WooCommerce are split across the order's payments. WordPress
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. 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 -
A Mobile Money charge above the per-transaction ceiling answers
422, in sandbox as in live. 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, 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;GET /links: an unknowntype;GET /events: atypeno endpoint receives;GET /channels: an unknowncountry;GET /countries: an unknownregion;GET /currencies: atypeother thanfiat,cryptoorall, read whatever its case.
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. Reading a transfer no longer counts in the transfers share. 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./healthreturns 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. 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 can answer
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 (Checkout — 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 staysprocessing, 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 that fails 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. 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
- 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. Shield API
Orchestration
- 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. 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 HTTP client (at least 90 s), 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. With two checkouts on a page, each 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. 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 Node.js and C# 2.0.0, PHP 2.0.1, Ruby 1.3.0, Python, Go and Java 1.2.1: 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 and Android 1.2.1, and Flutter from its next release, 1.6.0: an error that
trying again cannot fix, such as an ended session, is no longer marked
retryable. React Native and Android cancel the payment when the payer closes the sheet before submitting, and Flutter'sshowWajubPaymentSheetnow 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. 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. WordPress
- WordPress plugin 1.2.1: a partial refund started from WooCommerce is recorded once. MemberPress sends the transaction receipt.
- 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. Webhook payloads are still shaped for the latest
version, whatever your account's.
Versioning
API Sync Shield Konsole
- A dispute you accept or close now carries
metadata.resolutionset toaccepted. Disputes POST /beneficiaries/{id}/verifynow asks the operator for every account. Outside Sync Relay, check again any beneficiary checked before this release 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: React Native from 1.4.1 and Android from 1.2.1, their first published releases with them, and Flutter from its next release, 1.6.0. Cards on the payment provider's own secure page, and one-tap wallets such as Djamo. 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. 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- 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, recorded events and webhook deliveries, and the copies a Sync platform received. 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. 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. 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.
- 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,/shieldand/taxanswer403withX-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.customer.deletedcarries the anonymised record and is no longer followed bycustomer.updated. The customer's details are removed from the events already recorded and a repeated call answers200instead of404. Deleting a payer- On a charge made with a secret key from your own checkout, call
Wajub.signals()(Wajub.js 1.6.0) in the payer's browser and pass the result unchanged asdata._client. 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 - Importing
wajub.mjsfromjs.wajub.comas 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 release of the new
wajub listen; 1.6.0 was not released.
2026-10-02
CLI
- CLI 1.6.0:
wajub listenhands your handler 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. Payout writes (creating beneficiaries, transfers and batches) are capped at 20 a minute on every plan, Enterprise included. 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 for up to 72 hours, a4xxincluded. 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 andshield->addToBlocklist()the updated blocklist. Cursor pagination is followed bygetNextPage()andautoPagingIterator(). PHP SDK
2026-09-11
Payments Transfers CLI
- Djamo wallets, for payments and for beneficiaries.
wajub logoutrevokes the keywajub logincreated.
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