Skip to main content

Partner Wallet Contract

When a customer confirms a purchase, Pagamio calls your wallet API. This page defines the standard contract Pagamio publishes. A partner whose API conforms needs configuration only (base URL, credential, amount unit and timings). A partner whose API differs can agree a per-partner mapping with Pagamio; the behaviour described here (hold, then capture or release, with idempotent retries) stays the same.

The Model​

Funds move in three steps, keyed by one reference:

StepWhenWhat your wallet does
HoldCustomer confirms; before the product is bought.Reserve the amount on the customer's wallet so it cannot be spent elsewhere.
CaptureThe product purchase succeeded.Turn the hold into a final deduction of the held amount.
ReleaseThe product purchase failed, or the outcome could not be confirmed as a success.Free the held amount.

Every call carries the reference: Pagamio's payment-leg identifier (a UUID) for that purchase. It is the same on the hold, the capture, the release and any retry, and it is your idempotency key.

Conventions​

Base URL: your wallet base URL, agreed at onboarding. Paths below are relative to it.

Headers on every call

HeaderValue
AuthorizationApiKey <credential> (the credential you give Pagamio at onboarding)
Content-Typeapplication/json
X-Correlation-IdThe payment-leg id. Log it; it lets both sides trace one purchase.

Currency and amounts: currency is ZAR. By default amount is an integer in minor units (cents: R19.99 is 1999). If your API takes whole-currency decimals (19.99) tell Pagamio at onboarding and the unit will be configured for you. Amounts never have more than two decimal places.

Timeouts: Pagamio connects with a 5 second timeout and reads with a per-partner timeout (default 15 seconds). Answer quickly; a call that times out is treated as an unknown outcome (see Idempotency).

Hold​

POST /holds

{
"reference": "b1f5a2c0-8a1e-4f0b-9d4e-2c6a1f7e9a10",
"customerId": "customer-123",
"walletId": "wallet-456",
"amount": 2500,
"currency": "ZAR",
"description": "Vodacom Airtime R25"
}

customerId and walletId are the values you supplied when you created the session (or, for a launch, the token's sub for both). description is the product name.

Your responseMeaning to Pagamio
201 with {"holdId": "...", "status": "HELD"}Hold placed. Pagamio stores holdId and buys the product.
409 with "status": "HELD" in the bodyA hold for this reference already exists. Treated as success (an idempotent replay).
409 with any other statusGenuine conflict. The purchase is declined.
409 with no readable statusUnknown. Pagamio queries the hold by reference (see Query A Hold). It releases only a hold it can identify; otherwise the hold is left to expire at its TTL.
422 with {"reason": "<REASON>"}Definite refusal; see reasons.
401 / 403Pagamio's credential was rejected. Purchase declined.
404Declined.
Any other 4xxDeclined.
408, 429, any 5xx, or no answer in timeUnknown outcome. Pagamio queries the hold by reference. If it is HELD, the purchase continues. Otherwise the purchase is declined with a timeout message, and Pagamio releases only a hold it can identify; a hold it cannot identify is left to expire at its TTL.

Decline Reasons​

Return one of these reason values with a 422. They decide the message the customer sees.

reasonCustomer sees
INSUFFICIENT_FUNDS"Insufficient funds in your wallet."
CUSTOMER_NOT_FOUND"We couldn't find a wallet for your account. Please contact our support."
WALLET_BLOCKED"Your wallet is not active. Please contact our support." (use this for an inactive, suspended or frozen wallet)
LIMIT_EXCEEDED"This purchase is over your wallet's limit."
VALIDATION"The purchase details were not accepted."
AUTHENTICATION, DUPLICATE, or any other value"Something went wrong. Please try again."

Customer-facing wording is fixed by Pagamio. Text from your response is never shown to the customer.

Capture​

POST /holds/{holdId}/capture

{
"reference": "b1f5a2c0-8a1e-4f0b-9d4e-2c6a1f7e9a10",
"amount": 2500,
"vasReference": "core-transaction-id",
"receiptSummary": "Vodacom Airtime R25"
}

Capture is for the full held amount (partial capture is not used). vasReference is Pagamio's transaction id for the product purchase, useful for your own reconciliation.

Your responseMeaning to Pagamio
200 with {"status": "CAPTURED"}Done. If you return a captureId, Pagamio records it as your reference for the deduction; otherwise it records the hold id.
409 with "status": "CAPTURED"Already captured. Treated as success.
409 with another or unreadable statusDeclined (other status) or retried (unreadable).
410The hold has expired or no longer exists. Escalated to Pagamio operations for manual resolution.
Other definite refusals (401, 403, 404, 400...)Escalated to Pagamio operations.
408, 429, 5xx, timeoutRetried, see below.

Release​

POST /holds/{holdId}/release

{
"reference": "b1f5a2c0-8a1e-4f0b-9d4e-2c6a1f7e9a10",
"reason": "core_declined:AGGREGATOR_DECLINED"
}

reason is a short machine-readable note for your records.

Your responseMeaning to Pagamio
200 with {"status": "RELEASED"}Done. An optional releaseId is recorded as your reference.
409 with "status": "RELEASED"Already released. Treated as success.
Other definite refusalEscalated to Pagamio operations.
408, 429, 5xx, timeoutRetried.

Query A Hold​

GET /holds?reference=<reference>

Pagamio uses this to resolve a hold whose response it never received. Answer as follows:

Your responseMeaning to Pagamio
200 with {"holdId": "...", "status": "HELD"}The hold exists. The purchase continues, and Pagamio addresses later capture and release calls by this holdId.
200 with "status": "CAPTURED" or "RELEASED" (including an expired hold)Nothing to release. The purchase is declined with a timeout message and no release is sent.
200 with any other statusA live hold in an unrecognised state. Pagamio releases it by its holdId and declines the purchase.
404No hold exists under this reference. Pagamio sends no release.
5xx, timeout, or a 200 with no readable statusUnknown. Pagamio releases only if it already knows the hold id; otherwise it sends nothing and the hold lapses at its TTL.

A 200 must include holdId: it is how a later capture or release addresses the hold. Pagamio never sends a release without a hold id.

If you do not offer this endpoint, a hold whose response was lost is not released by Pagamio; it stays reserved on your side until it expires at its TTL. Implementing the query is therefore strongly recommended, and a hold lifetime that is not excessively long matters (while staying above the minimum in Timing Requirements). Tell Pagamio at onboarding if you cannot offer it.

Idempotency And Retries​

  • Same reference, same result. Repeating a hold, capture or release with the same reference must not move money twice. Return the current state instead (409 carrying the status, or the original success).
  • Pagamio retries unknown outcomes. Capture and release calls that time out or return 408, 429 or 5xx are queued and retried with increasing delays: 30 seconds, 1 minute, 5 minutes, 15 minutes, then hourly. If they are still unresolved after 24 hours, the purchase is escalated to Pagamio operations for manual resolution. Retries reuse the same reference.
  • Definite refusals are not retried. A 4xx refusal on capture or release is a decision, so it escalates straight away.
  • The hold call is retried only by way of the query. Pagamio does not blindly re-send a hold whose answer it lost: it queries first (see above), and only releases a hold it can identify.
  • Return 2xx or a definite 4xx promptly. A slow 5xx looks the same as an outage.

Consistency With Pagamio's Records​

Each business day Pagamio can give you a statement of every payment leg (see Transactions And Statements). Use it to reconcile your ledger against holds, captures and releases. Differences that persist are investigated with Pagamio operations using the reference and vasReference.

Requirements For Your Wallet API​

  • HTTPS with a valid certificate.
  • A stable hold that remains valid for at least the period in Timing Requirements.
  • One hold per reference. Reference reuse across customers is not expected.
  • Idempotent hold, capture and release as above.
  • A reachable sandbox that behaves like production.

Last updated: September 2026