Payment Gateway 6.1.0

31 July 2026

This release has upgrade notes. Read them before updating — they describe behaviour changes that need your attention.

SECURITYCHECKOUTWEBHOOKSPAYOUTSMERCHANTSKYCSCHEDULED TASKSUIBREAKING CHANGES

Payment Gateway v6.1.0

Release Date: July 31, 2026 Tags: SECURITY, CHECKOUT, WEBHOOKS, PAYOUTS, MERCHANTS, KYC, SCHEDULED TASKS, UI, BREAKING CHANGES

Overview

Version 6.1.0 closes a hole in crypto checkout. On every crypto payment the buyer's browser told the server what its coins were worth, and the server accepted that figure as the amount paid. A crafted client could claim up to 2% more value than its coins were actually worth, be debited the same coins, and still have the payment accepted in full. Merchants were credited for orders that were short by up to 2%, with nothing on the payment to show it. Settlement now uses the platform's own rate.

It also makes two documented promises real for the first time: webhooks are now genuinely retried until your server acknowledges them, and checkout sessions now expire on their own. Both were described in the integration guide and neither existed. In the same release, webhook deliveries stop being signed with an empty key — a merchant verifying the signature the way the guide describes has been rejecting every event this gateway has ever sent.

It also repairs merchant payouts. On an installation where nobody had ever touched the Enable Gateway switch, payout processing skipped every single run and reported success, while checkout carried on taking money. An admin rejecting a payout had it recreated, identically, by the next run.

Five steps below need your attention, including a backlog you should expect on the first scheduled runs.

Requires Core v6.5.8.

Update Instructions

pnpm updator

Then restart the backend. The two new scheduled tasks are registered at boot, and the new business type field on merchant records is created at startup — there is no manual database work.

pm2 restart all

Then, in this order:

1. Confirm the scheduled tasks are running

Open Admin → System → Scheduled Tasks and check the gateway category lists three entries: Process Gateway Payouts (hourly, existing), Retry Failed Gateway Webhooks (every minute, new) and Expire Lapsed Gateway Payments (every 5 minutes, new).

2. Expect a backlog on the first runs

Both new tasks pick up history that has been accumulating since the gateway was installed:

  • Expiry takes up to 500 lapsed sessions per run and stops after 60 seconds of work, deferring the rest to the next run. Every one of them sends a payment.expired webhook to the merchant that owns it. A store with months of abandoned checkouts will see them close out over the following runs, not all at once.
  • Webhook retries take up to 100 past-due deliveries per run. Anything still marked for retry from before this release is due, so merchants may receive older events — correctly signed, most of them for the first time.

If you have merchants on production integrations, tell them before you update.

3. Review pending payouts

If nobody on this installation had ever changed the Enable Gateway switch in Admin → Gateway → Settings, payout processing has been skipping every run while payments kept completing. The first run after this update will create payouts for balances that have been accruing the whole time. Review Admin → Gateway → Payouts before approving anything — expect a larger batch than usual, and check it against the merchants' payment history.

4. If you enforce KYC per feature, grant the gateway feature

Merchant onboarding and API-key creation now ask for the shared Payment Gateway Usage feature. This only matters if Settings → Features → Verification → Enforce KYC Feature Access is switched on: in that case, add Payment Gateway Usage to every KYC level in Admin → CRM → KYC → Levels that should be able to register as a merchant or issue keys, or those doors will refuse fully verified users. With enforcement off — the default — the previous bar applies unchanged.

5. Third-party callers of the public statistics figures

See Upgrade Notes below.


Upgrade Notes

The public gateway statistics call has been removed

The public, unauthenticated gateway statistics call has been removed. It returned four figures — total merchants, total transactions, volume in millions, and success rate — to anyone who asked, with no key.

  • Callers now get "not found".
  • The same four figures are available from the public landing data the gateway marketing page already loads, under its statistics section. No first-party page ever used the removed call; the marketing page has been reading the landing data all along.
  • The published API reference still lists the removed call. That listing is stale and will be corrected.
  • The admin statistics view is unaffected.

Compatibility

  • Requires Core v6.5.8.
  • A backend restart is required. The two new scheduled tasks are registered at boot, and the new business type field on merchant records is created at startup. There is no manual database work and no configuration change.
  • Merchants will start receiving payment.expired for sessions that were abandoned before this release. If your merchants' integrations do not handle that event, they will see it for the first time; the event and its payload are unchanged from the published list.
  • payment.failed volume drops. Any integration that has been treating it as "the buyer's card was declined, try again" will simply stop seeing those cases. Any integration that treats it as terminal is now correct to.
  • Payout figures. Gross amount, payment count and refund figures on payouts created from now on describe only the payments behind that payout. Existing payout records are not rewritten, so a statement spanning the update will mix the old overlapping figures with the new ones. Amounts actually paid are unaffected and were always correct.
  • Payout creation resumes on installations where the Enable Gateway switch was never touched. Expect a batch of payouts awaiting approval on the first run.
  • KYC: no change unless Enforce KYC Feature Access is switched on, in which case Payment Gateway Usage must be granted to the levels that should be able to register as merchants or issue API keys.
  • Checkout designs, webhook secrets, API keys and merchant records are otherwise untouched.

Added

Failed merchant webhooks are now retried instead of being abandoned

A failed delivery has always been recorded as "retrying", with a time to try again — and nothing ever read either of those. There was no retry. A merchant whose server blipped for a minute lost that event permanently, while the integration guide promised every event is retried until acknowledged.

  • Added a scheduled task that runs every 60 seconds and redelivers everything that is due. The backoff is 1 minute, then 5 minutes, 30 minutes and 2 hours; after the fifth attempt the delivery is marked failed, so a merchant has roughly two and a half hours of outage before an event is given up on.
  • Added adoption of deliveries stuck at "pending" for more than 2 minutes. That is the state left behind when the process dies between recording a webhook and sending it — a deploy, a restart, a crash — and previously nothing would ever look at those rows again. The 2-minute grace keeps the task off deliveries whose first attempt is still in flight.
  • Added delivered-versus-still-failing counts in the scheduled tasks panel, so an operator can see whether a merchant's server is actually accepting events.
  • Fixed: every webhook this gateway sent was signed with an empty key rather than the merchant's own webhook secret. A merchant verifying the signature — which is exactly what the integration guide tells them to do — rejected all of them, so retrying an unsigned event would have achieved nothing. Deliveries are now signed with the merchant's real secret, and a delivery whose secret cannot be resolved is recorded as failed rather than sent in a form the receiver cannot tell apart from a forgery.

Abandoned checkout sessions now expire on their own

Expiry was lazy: a payment became expired only when someone reloaded the checkout page or posted a confirmation. An abandoned session is by definition one nobody returns to, so those sessions sat pending forever, and the payment.expired event in the published list could never fire at all.

  • Added a scheduled task that runs every 5 minutes over up to 500 lapsed sessions, closes them out and sends payment.expired to the merchant. Integrations that wait on that event to release reserved inventory finally receive it, and merchant dashboards stop accumulating payments that never resolve either way.
  • Added a 60-second budget per run. A merchant whose server silently swallows requests costs the full 30-second send timeout per payment, and one such merchant with a weekend of lapsed sessions would otherwise block every other merchant's expiry behind it. Sessions not reached stay pending and are taken by the next run, oldest first.
  • A payment already being confirmed is deliberately left alone, so a session cannot be expired out from under a buyer whose funds are mid-debit.
  • The window itself is unchanged — it is still the Payment Expiration (minutes) setting in Admin → Gateway → Settings.

Changed

The payment.failed event is no longer sent for recoverable checkout errors

The failure event fired on every error except insufficient funds. That meant a buyer who hit "Exchange rate has changed for X. Please refresh and try again." — a routine staleness check that fires while the payment is still pending and the buyer is still sitting on the checkout page — sent the merchant a terminal payment.failed, which integrations treat as "cancel the order". Merchants were told a payment had failed and then watched the same payment succeed. It was reachable on any rate move outside the 2% window.

  • Changed payment.failed to fire only for genuine server-side faults. A session that is refused but still retryable no longer produces it, and a session that truly dies is reported by payment.expired from the new expiry task instead.
  • Integrations that key off payment.failed will receive materially fewer of them, and the ones they do receive mean what the event has always claimed to mean.

Merchant onboarding and API-key creation now use the platform KYC feature gate

The gateway's own Require KYC for Merchants switch still decides whether to ask for verification. What it asks for is now the shared Payment Gateway Usage feature, which operators configure per level in the KYC level builder rather than as an all-or-nothing check for "any approved application".

  • Changed a refusal to be a proper permission refusal rather than a malformed-request error, so the pages can tell the difference between "you are not verified" and "your level does not include this".
  • Changed the registration form and the API-key creation page to show a real KYC notice instead of sending a user who already reads 100% verified back to a verification page they have finished.
  • Unchanged on upgrade: while Enforce KYC Feature Access is off — the default — the previous bar stays in force exactly as before, so an installation running with Require KYC for Merchants switched on today keeps the same requirement. Nothing is loosened by updating.
  • The API-key gate sits after the freshly-issued-keys screen, so a key that was just created is never hidden from the person who created it.

Merchant navigation regrouped, with three pages that had no way in

  • Changed Payments and Payouts to sit under a Transactions menu, and Integrations, API Reference, API Keys and Webhooks under a Developers menu. Six flat entries become five.
  • Added menu entries for the API reference, which had none at all, and for the API-key and webhook panels, which were only reachable from a button on the dashboard. All three are now two interactions away — open the menu, click the entry.
  • Existing menu translations are kept exactly as they were, and any new entry with no translation falls back to its English title, so the other 89 languages still read correctly.

The gateway landing page is rebuilt on the shared landing components

  • Changed the public marketing page off the generic page-builder composition — with its interactive pattern, floating shapes, blurred orbs and particle field — onto the platform's shared landing components, with the hero and headline figures compacted.
  • Changed the activity feed and supported-currency tiles to solid panels instead of translucent plates that mixed with whatever happened to be behind them, and the per-coin colour map is replaced with ticker chips. The page now reads consistently in both light and dark themes.
  • Changed the activity feed to respect the visitor's reduced-motion preference.
  • No data and no behaviour changed on this page — the figures shown are the same ones, from the same place.

Money figures and stat cards consolidated onto the shared components

  • Changed amounts on payment detail, payout detail, the admin payout drawer, checkout summaries and the balance panels to render through the one shared money component with aligned numerals, so columns of figures line up.
  • Changed the bespoke gradient stat cards on the payouts page, the admin merchant detail page and the merchant dashboard to the shared stats card.
  • Changed the admin settings tabs from fixed blue, emerald, amber, red and purple to the platform's own palette, so an operator changing the theme actually reaches these pages.

Fixed

Crypto checkout settled on the buyer's own exchange-rate quote

On every crypto payment, the buyer's browser sent the server what it believed its coins were worth in the merchant's currency, and the server used that number as the amount paid. It was checked only for staleness — within 2% of the platform's own rate — and being 2% high passed that check. A crafted client could therefore claim up to 2% more coverage than its coins were worth, be debited exactly the same crypto, and have the payment accepted as paid in full. The merchant was credited for an order short by up to 2%, and the wallet record, the payment's stored equivalent amount and the coverage decision all repeated the buyer's figure, so nothing anywhere showed the shortfall.

  • Fixed settlement so that coverage, the wallet transaction description and the recorded equivalent amount all use the platform's own rate. The 2% window remains what it was always meant to be — a staleness check on the buyer's quote, not permission to use it. The buyer's quoted figure is kept alongside the settled one on the payment for reconciliation.

Payout processing silently skipped every run on installs that never touched the gateway switch

The payout task read the Enable Gateway switch directly and treated a missing value as "off". Saving the settings screen only stores what you changed, and nothing seeds this switch, so an operator who never touched it had no stored value at all. Checkout was unaffected — it defaults the same setting to on — so payments completed normally while every payout run reported "completed" having created nothing, and merchant pending balances accrued indefinitely.

  • Fixed the switch so it is read with the same default the rest of the gateway uses, so an untouched installation is treated as enabled — as checkout always did. A gateway that is genuinely switched off now logs the skip as a warning rather than an ordinary note, so it is visible in the scheduled tasks panel.

An admin rejecting a payout had it recreated by the next run

Rejecting a payout deliberately leaves the funds in the merchant's pending balance. The duplicate check only looked for payouts that were pending, processing or completed, so the next run saw an unclaimed balance and created the identical payout for the identical period. A rejection could never take effect.

  • Fixed the duplicate check, so cancelled and failed payouts now block re-creation for the same period, and the log says which status is blocking.
  • Fixed: instant-schedule merchants needed a separate guard. Their period always ends at "now", so no two runs ever compute the same period and the period check could never match. An instant payout rejected in the last 24 hours now suppresses re-creation for that merchant, currency and wallet combination.
  • Unchanged: the money. This is a gate on creating a duplicate, never a deduction from the merchant's balance — a later period pays the merchant normally.

Merchants could never edit their own business profile

The profile lock was "anything other than unverified", and registration creates every merchant as pending — so unverified was a state no merchant was ever in, and the lock engaged the instant the account was created. Name, email, phone, website, address, city, state, postal code and country were frozen from day one, for details nobody had reviewed yet, and the only remedy offered was a message telling the merchant to contact support. The settings screen disabled the same fields on the same wrong basis, so it was at least consistent — consistently wrong.

  • Fixed the lock, which now engages on verified only. A merchant can correct their own record right up to the point an admin approves it, which is what the lock is actually for: approved details cannot be swapped out behind the reviewer's back. The settings screen and the save both decide it the same way, once.
  • Fixed: a verified merchant could be refused on save for a field they had not changed. The form sends an empty value where the record holds no value, which counted as an edit to a locked field and rejected the entire save — including the description, which is not a locked field at all.

Payout statement figures double-counted payments

The gross amount, payment count and refund figures written onto a payout were computed over the payout schedule's whole period. For an instant schedule that window is the last 24 hours and slides with every hourly run, so the same payments were attributed to payout after payout after payout. Summing those figures across a merchant's payouts — a statement, a revenue report — silently inflated the totals.

  • Fixed the figures, which now start where the previous payout's period ended, when that is later, so each payout describes only the payments behind it.
  • Unchanged: the money actually paid was always correct. Only the descriptive figures were wrong, which is why this went unnoticed.

Business type asked for at registration was thrown away

The registration form has always asked for a business type — "e.g. E-commerce, SaaS, Services" — and the answer went nowhere. There was nowhere to store it, registration never wrote it, saving the profile did not accept it, and the profile never returned it. The question was asked of every merchant and the answer discarded every time.

  • Fixed business type, which is now stored, editable, returned with the merchant profile, and shown in the Business Information card an admin reads when reviewing an application.

The merchant dashboard showed invented growth percentages

The Payments (30d) and Net Revenue (30d) tiles carried +12.5% and +8.3% trend badges with green up-arrows. They were fixed text written into the page. Nothing computed them, and they never changed no matter what the merchant's real numbers did.

  • Removed both rather than restyling them. There is no prior-period comparison available to put in their place, and a fabricated trend on a revenue figure is worse than no trend. The tiles now show only measured figures.

Three hosted-checkout designs showed raw translation keys to buyers

On Cyber Tech, the main pay button read confirm_payment instead of "Confirm Payment", and the wallet-unavailable state read not_available on Cyber Tech, Luxury Gold and Purple Glass. The labels were being looked up in places that do not contain them, so the untranslated name was printed instead — in all 90 languages.

  • Fixed all four, which now read from the set that has them. The platform default checkout design, Dark Premium, was never affected, so this only reached installations that had selected one of the three in the checkout design picker.

Payment and payout status colours disagreed between screens

Seven screens each kept their own private table of status colours, and they did not match. Most visibly, a partially refunded payment was purple on the admin payment detail screen and orange on the admin dashboard — the same payment changed colour between the list and its own detail page.

  • Fixed every screen to take its colour from one shared table, keeping only its own icon and label. Four transaction tables had local colour overrides removed so they stop quietly contradicting it.

The API reference and integration code samples were forced dark in light mode

Code blocks on the API reference and integration pages were fixed to a near-black background with light text whatever the theme, so a developer reading the documentation in light mode got a black slab in the middle of a light page. The header band above them was an accent fade that was invisible in light mode except for its bottom border.

  • Fixed code blocks, the copy button and the header band, which all follow the theme now. Method badges and the error table moved from fixed blue, green, yellow and red to the platform's own colours, so they change with the theme too.

The payout detail page rendered underneath the site header

Opening a payout rendered its content with no page frame at all — no container, no side gutters, no clearance for the fixed header. The payout ID and back button sat behind the header, and the content ran edge to edge.

  • Fixed the page, which now uses the standard page frame and has the same gutters as the rest of the product.

The merchant Test/Live chip was hard to read in light mode

The Test/Live indicator painted coloured text on a tint of the same colour at 12px, which does not reach readable contrast in light mode.

  • Fixed the label, which is now normal foreground text on a bordered tinted chip, with the status colour moved to the icon, so the mode is also distinguishable without relying on colour alone.

The public fee calculator could quote a negative payout

The net figure was the amount minus the fee with no floor, so with the default 30-cent fixed component an amount of 0 printed "You receive -$0.30" on the public marketing page.

  • Fixed the net figure, which is never below zero now. The amount box is also limited to 0–10,000, where it previously accepted anything, and a typed amount above the slider's $1,000 track now pins the handle at the end of the track instead of appearing to reset what you typed. Figures render with thousands separators in aligned numerals.

Balance rows on the payouts page identified themselves by something that is never sent

The payouts page listed merchant balances using an identifier the balance data has never included, so every row was effectively unidentified and produced a console warning.

  • Fixed the rows, which are identified by wallet type and currency — what actually distinguishes them — and the field that was never there has been removed.