Integrating with Wynk Systems
One versioned HTTP API over the same domain packages the portals use. There is no second, weaker path into the core for integrations.
Six rules that hold everywhere
Authentication is per request, and authority is never client-asserted
A caller presents a credential; the server resolves the tenant and the roles. A client cannot assert a role, a tenant, or a user id — those are derived server-side and any request that tries to supply them is refused.
Every money POST carries Idempotency-Key
A replay with the same key returns the ORIGINAL outcome marked already-processed. A replay with the same key and a different body is a conflict, not a second movement. Non-money writes accept the header and honour it the same way.
Amounts are integer minor units, always
Every amount field is named *_cents and carries an integer. There is no decimal amount and no implied scale. A currency travels with the amount; a bare number is refused.
Errors are a closed taxonomy
Every failure is one of a fixed set of wynk.* codes with a stable shape: code, message, and structured details. An integrator can exhaustively switch on the code, and a new failure mode does not invent a new string.
Reads are keyset-paginated
Collection endpoints return a cursor, not an offset. A page boundary is stable while the collection is being written to, which offset pagination cannot promise.
Webhooks are signed, ordered and replayable
Deliveries carry a signature over the canonical body, a delivery audit chain and a dead-letter queue. A consumer that was down replays from the last acknowledged delivery rather than losing the window.
The endpoints
Read surfaces are keyset-paginated. Every endpoint that moves money is marked, and every one of those requires an idempotency key.
| Endpoint | What it does | Class | Replay protection |
|---|---|---|---|
| GET /v1/dashboard | Tenant-scoped portfolio summary | Read or configuration | — |
| GET /v1/loans | Loan facilities, keyset-paginated | Read or configuration | — |
| GET /v1/loans/:facilityId | One facility with its schedule and arrears state | Read or configuration | — |
| POST /v1/loans/unsecured | Originate an unsecured facility (NCA affordability enforced) | Moves money | Idempotency-Key required |
| POST /v1/loans/:facilityId/disburse | Disburse a facility (two-eyes) | Moves money | Idempotency-Key required |
| GET /v1/wallets | Wallets and balances in minor units | Read or configuration | — |
| GET /v1/wallets/:walletId | One wallet with its holds and pockets | Read or configuration | — |
| POST /v1/wallets | Open a wallet against a ledger account | Read or configuration | Idempotency-Key required |
| POST /v1/wallets/:walletId/deposit | Credit a wallet from a settled inbound | Moves money | Idempotency-Key required |
| POST /v1/wallets/:walletId/transfer | Move value between wallets | Moves money | Idempotency-Key required |
| GET /v1/vas | VAS catalogue and transaction history | Read or configuration | — |
| POST /v1/vas/purchase | Buy airtime, data, electricity or a bill payment | Moves money | Idempotency-Key required |
| POST /v1/payments/instruct | Instruct an outbound payment onto a rail | Moves money | Idempotency-Key required |
| GET /v1/ledger/facts | Accounting facts with their legs | Read or configuration | — |
| GET /v1/accounting/facts | Fact stream for reconciliation | Read or configuration | — |
| GET /v1/accounting/walk-back/:factId | Walk a fact back to its origin event | Read or configuration | — |
| GET /v1/accounting/legs/:legId/walk-back | Walk a statement line back to its origin | Read or configuration | — |
| POST /v1/accounting/chart/ensure | Ensure the chart of accounts for a tenant | Read or configuration | Idempotency-Key required |
| POST /v1/accounting/afs/:tenantId/generate | Generate annual financial statements for a framework | Read or configuration | Idempotency-Key required |
| POST /v1/chain/verify | Recompute the audit and ledger chains from origin | Read or configuration | Idempotency-Key required |
| POST /v1/compliance/disclose | Zero-knowledge prudential disclosure to a regulator | Read or configuration | Idempotency-Key required |
| GET /v1/audit | Hash-chained audit events | Read or configuration | — |
What a call looks like
POST /v1/loans/unsecured
Authorization: Bearer <tenant credential>
Idempotency-Key: 8f1c0a3e-4d2b-4a19-9f7e-2c6d1b0a5e34
Content-Type: application/json
{
"borrower_person_id": "…",
"principal_cents": 2500000,
"currency": "ZAR",
"term_months": 24,
"purpose_code": "consolidation"
}
HTTP/1.1 200 OK
Idempotency-Status: replayed
{
"facility_id": "…",
"status": "originated",
"already_processed": true
}
HTTP/1.1 409 Conflict
{
"code": "wynk.conflict",
"message": "consumer is under debt review",
"details": { "reason": "nca_s86_debt_review", "since": "2026-03-11" },
"correlation_id": "…"
}
GET /v1/accounting/legs/<legId>/walk-back
{
"leg": { "account_code": "1104-loans-sme", "amount_cents": -450000, "side": "C" },
"fact": { "id": "…", "chain_seq": 918342, "hash_row": "…" },
"origin_event": { "kind": "loan.repayment", "at": "…" },
"workflow_step": { "id": "…", "approved_by": "…", "two_eyes": true },
"mandate": { "kind": "debicheck", "reference": "…" }
}
The error taxonomy
Closed, so an integrator can switch exhaustively. A new failure mode maps onto an existing code or the taxonomy changes in a versioned release — it never appears as a new free-text string.
| Code | HTTP | Meaning |
|---|---|---|
| wynk.validation | 400 | The request shape or a field value is invalid. The details name the field. |
| wynk.unauthenticated | 401 | No usable credential. Uniform for absent, malformed and expired — deliberately not an oracle. |
| wynk.forbidden | 403 | Authenticated, but the action is outside this caller's authority — including a browser-security refusal. |
| wynk.not_found | 404 | No such resource within this tenant. A resource in another tenant is not found, not forbidden. |
| wynk.conflict | 409 | A lifecycle or uniqueness precondition failed — including an idempotency key replayed with a different body. |
| wynk.rate_limited | 429 | The caller exceeded a route quota. The response carries the retry window. |
| wynk.upstream.unavailable | 502 | A required counterparty or provider is unreachable. The operation is refused, never partially applied. |
| wynk.internal | 500 | An unexpected fault. The response carries a correlation id and nothing else. |
Webhooks
- Every delivery is signed over the canonical body, so a consumer verifies authenticity without a shared transport secret in a query string.
- Deliveries carry a sequence and a delivery audit chain — a consumer detects a gap rather than assuming completeness.
- Failed deliveries retry with exponential backoff and land in a dead-letter queue that is visible in the admin portal, not in a log nobody reads.
- Replay is supported from the last acknowledged delivery, so an outage on your side costs you latency, not events.
Environments
- Sandbox — your own tenant, fixture data, mock counterparties. Every rail responds; none of them move real value.
- Pre-production — your own tenant, your migrated data, live counterparties in their test modes.
- Production — cutover happens with a chain attestation on both sides, so the opening balances you carry across are evidenced rather than asserted.
Integration questions go to developers@wynk.systems. If you are evaluating, the fastest route is a sandbox tenant — start at onboarding.
Wynk Systems is a bank-grade core-banking platform. Registered in South Africa.
This site is informational: nothing on it constitutes an offer, a quotation, or credit advice.