Overview
A top-up credits a customer’s wallet with actual, spendable money. Feddi credits the ledger after the payment settles; Feddi never holds the cash. You can credit a wallet two ways:- Confirm an online-gateway top-up with
POST /topup/confirm. - Report a cashier-sold reload SKU with
POST /topup/sku/{skuId}.
Money is always a minor-units integer paired with an explicit ISO-4217 currency (for example
amount_minor: 5000, currency: QAR). Call GET /capabilities and GET /openapi at runtime for the authoritative list of operations, currencies, and credential types available to your integration.How a top-up settles
The customer pays
For an online top-up, the customer pays on the provider’s hosted page and the cash settles to the merchant’s bank. For a cashier-sold reload, the customer pays the reload SKU at the till.
You report the settled payment
Call
POST /topup/confirm (online) or POST /topup/sku/{skuId} (SKU). Feddi credits the actual balance and accrues any configured reload bonus as a promotional grant in the same atomic transaction. Both effects apply together or neither does.Feddi returns both money classes
The response carries
credited_minor (actual money) and bonus_minor (promotional grant) as distinct fields, plus the balances after the credit.provider_payment_ref), or report a cashier-sold SKU sale against a finalized POS order reference (pos_order_ref).
Idempotency
Both write endpoints are idempotent on theIdempotency-Key header, a partner-generated UUID with a 24-hour TTL. Send it on every mutating call.
- Same key, same payload: byte-identical replay.
meta.idempotency_replayedistrue. - Same key, different payload: the call returns
IDEMPOTENCY_KEY_REUSED(HTTP 422).
POST /topup/confirm deduplicates a second way: on the provider settlement reference (provider_payment_ref). A duplicate provider notification plus a partner retry credit the wallet exactly once.
POST /topup/sku/{skuId} deduplicates a second way: on the POS order reference (pos_order_ref). A duplicate sale report never double-credits.
Confirm an online-gateway top-up
POST /topup/confirm credits the customer’s actual balance and accrues any configured reload bonus, then settles both in one atomic transaction. The credited amount is server-authoritative: Feddi derives it from the finalized provider settlement event and reconciles it against the amount_minor you send. Because the customer is already identified at top-up time, the reload bonus accrues directly to released with no claim gate.
Authenticate with a POS terminal JWT.
Request
required
Partner-generated UUID. The dedup key for the call. 24-hour TTL.
required
The request context.
merchant_id is required and is enforced against the credential’s authority; a merchant_id outside that authority is rejected. Optional members: branch_id, terminal_id, cashier_id, partner_session_id.required
The provider that processed the charge. One of
SADAD, STRIPE.required
The provider’s settled payment reference (for example a Stripe PaymentIntent id or a Sadad transaction number). Used as a secondary idempotency anchor.
required
ISO-4217 code, 3 characters. A currency with no active provider returns
CURRENCY_NOT_SUPPORTED.required
Expected paid amount in minor units, minimum
1. The credited amount is derived server-side from the finalized provider event and reconciled against this value. Nullable.required
A prepared provider session reference, when you have one. Nullable; omit it to confirm against
provider_payment_ref alone.required
Request metadata.
partner_request_id (UUID, correlation only, not the dedup key) and api_version are required when meta is present. Optional occurred_at and sent_at timestamps (RFC3339); send both when you have them.Response
required
The deposit transaction id.
required
One of
pending, completed, failed, escrowed. completed means the wallet was credited. escrowed means the credit is held for an unverified customer until they register.required
The credited wallet id.
required
Actual (cashable) balance credited, in minor units.
required
Promotional bonus accrued, in minor units.
0 when no bonus applied.required
Actual balance after the credit. Nullable;
null when the credit is escrowed.required
Released promotional balance after the accrual. Nullable.
required
ISO-4217 currency code of the credit.
required
The reload bonus accrued by this top-up, if any. See the fields below.
credited_minor is the actual-money credit. bonus_minor is the separate promotional grant. They are returned as distinct fields and accounted distinctly. Never sum them into one balance.Errors
Cashier-sold reloads (top-up SKUs)
Some merchants sell a reload as a line item at the till (for example a “QAR 50 wallet load”). When a registered top-up SKU is sold, report it withPOST /topup/sku/{skuId}.
Feddi credits the resolved wallet by the SKU’s registered wallet_credits_minor and accrues any configured SKU bonus as a promotional grant with source SKU_TOPUP_BONUS. The credited amount and bonus are derived server-side from the SKU registration and the finalized POS sale; POS-submitted money fields are ignored. The bonus accrues released because the customer is identified at point of sale.
If the customer is not yet a verified Feddi customer, the sale creates an unverified proto-wallet: the customer keeps the reload (actual balance credits) and the bonus stays locked behind proof-of-phone until they register. In that case status is escrowed and customer_state is pending_proof. See PENDING_PROOF and promo release.
Authenticate with a POS terminal JWT.
Request
required
Partner-generated UUID. 24-hour TTL.
required
The registered top-up SKU id (UUID). A SKU that does not exist, is retired, or belongs to another integration resolves to
NOT_FOUND.required
The request context.
merchant_id is required; optional branch_id, terminal_id, cashier_id, partner_session_id.required
The finalized POS order reference for this sale. Used as the server-side derivation source and the secondary idempotency anchor.
required
The identity credential, discriminated by
credential_type (one of phone, provider_customer_id, card_fingerprint, short_code, qr). For example a phone credential carries an E.164 phone value. The enabled set is gated by GET /capabilities.required
Number of SKU units sold, minimum
1, default 1. The credit is wallet_credits_minor multiplied by quantity.required
Request metadata.
partner_request_id and api_version are required when meta is present; optional occurred_at and sent_at.Response
The response is the sameTopupResult shape as POST /topup/confirm, with one additional field surfaced on the SKU path:
required
The resolved customer identity state at credit time. One of
verified, pending_proof. Nullable.Errors
Configure the reload bonus
POST /topup/reload-bonus/config sets the amount-banded bonus a wallet program grants on a top-up (for example “top up QAR 50 or more, get a 10% bonus”). The config is forward-looking only: it governs future top-ups and never re-grants retroactively. The bonus it produces accrues as a promotional grant, distinct from the customer’s actual cashable balance.
Authenticate with an API key. This call requires the enterprise-admin integrations permission.
Request
required
Partner-generated UUID. 24-hour TTL.
required
The request context.
merchant_id is required.required
The wallet program these tiers govern.
required
ISO-4217 code, 3 characters.
required
One or more non-overlapping bonus bands. Each tier carries
min_topup_minor, optional max_topup_minor, bonus_type, and bonus_value. See the tier fields below.required
How long an accrued bonus grant lives before it expires, in days, minimum
1. Drives the grant’s expires_at. Nullable.required
Request metadata.
partner_request_id and api_version are required when meta is present.Tiers must not overlap, and percentage values stay within the
0 to 100 range. An overlapping or out-of-range tier returns VALIDATION_ERROR.Response
required
The persisted config id (UUID).
required
The wallet program the tiers govern.
required
ISO-4217 currency code.
required
One of
active, inactive.required
The saved bonus bands, each with
min_topup_minor, max_topup_minor, bonus_type, and bonus_value.required
Grant lifetime in days. Nullable.
required
When the config was last saved (RFC3339).
Insufficient-funds recovery
When a wallet pay fails withINSUFFICIENT_FUNDS, top up the wallet and retry the charge. Take the customer’s gateway payment or sell a reload SKU, then call POST /topup/confirm or POST /topup/sku/{skuId} to credit the wallet before re-attempting the payment. See Payments and redemption for the charge retry.
Response metadata
Every response carries a uniformmeta block. Two fields you may see:
data_completeness_score: an opaque integer Feddi may use internally. No action required.decision_trace_id: an opaque correlation id. Persist it and echo it where a call accepts one.
Where to go next
Money: actual vs promotional
Why actual balance and reload-bonus promo are never one number.
PENDING_PROOF and promo release
How a reload credits while the bonus stays locked until phone proof.
Payments and redemption
The promo-first debit and the insufficient-funds moment that triggers a reload.
Incentives
The other sources of promotional grants beyond the reload bonus.