Download OpenAPI specification:
This API definition captures the Payee Service Provider API specification. The PeSP hosts this API for orchestration of enrichment flows.
This API does not cover the interfaces between the PeSP and their respective Payees and Payers. The API assumes that the following exchanges have been successfully completed before ths API is used:
A high-level sequence diagram of the subsequent exchange:
NOTE: The 'S_CARD_PULL' pull payment option is included in this API but is NOT supported. At this early stage of development it is included as example to illustrate how future pull mechanism will be implemented, but it has not yet been ratified for implementation.
Use the Pep 'resolve' endpoint as the starting point of a flow when your customer has scanned a PeP Paylink. The PeSP returns the following information after you have submitted a valid Paylink for resolution:
Use this endpoint to resolve a Payee-presented Paylink (PeP) and retrieve the payee identification information. This is called by the Payer Service Provider when a Payer scans or submits a PeP paylink.
required | object (NewPrSpTracker) A new PrSP message tracking object submitted by the PrSP when initiating a new flow |
required | any Information on the Paylink that was scanned by the Payer |
{- "tracking": {
- "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
- "payerSpMessageSeq": 1
}, - "paylink": {
- "kind": "URI_ENCODED",
- "anchorDomain": "pay.gov.za",
- "flowTypeIndicator": "general",
- "spIndicator": "xmpl",
- "paylinkValue": "823hf92hfg9nv9adsn98284",
- "presentmentFormat": "QR"
}
}{- "tracking": {
- "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
- "payerSpMessageSeq": 1,
- "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
- "payeeSpMessageSeq": 1
}, - "payeeIdentification": {
- "kind": "S_BUSINESS_BASIC",
- "companyName": "Big Company Pty Ltd",
- "siteName": "V&A Waterfront",
- "email": "email@example.com",
- "phone": "+27821234567",
- "customerServicePhone": "+27821234567",
- "physicalAddress": {
- "addressLine1": "2048 Binary Street",
- "addressLine2": "Suite 1024",
- "city": "Cape Town",
- "stateOrProvince": "Western Cape",
- "postalCode": "7140",
- "country": "ZA"
}
}
}Use the 'Payment Reply' endpoint to respond to a Payment Request logged to you by the PeSP.
Use this endpoint to send a payment reply from the Payer Service Provider to the Payee Service Provider during an open flow.
Request structure:
| Reply Type | Description |
|---|---|
| CONFIRM | Confirming a successful push payment |
| DELEGATE | Delegating a pull payment to the PeSP |
| DECLINE | The Payment Request was declined by the Payer |
| FAILED | The PrSP could not successfully initiate the payment |
| kind required | string Confirming a successful push payment Value: "CONFIRM" |
required | object (ActivePrSpFlowTracker) An active Payer service provider tracking object exchanged during a flow |
required | any The CONFIRMATION payload when a push payment was performed |
{- "kind": "CONFIRM",
- "tracking": {
- "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
- "payerSpMessageSeq": 1,
- "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
- "payeeSpMessageSeq": 1,
- "sessionInfo": {
- "status": "OPEN",
- "asAt": "2023-10-05T14:48:00.000Z",
- "expiresAt": "2023-10-05T14:48:00.000Z",
- "createdAt": "2023-10-05T14:48:00.000Z"
}
}, - "confirmation": {
- "kind": "S_EFT_PUSH",
- "accountNumberMasked": "xxxxxx7890",
- "branchNumber": "123456",
- "accountType": "CHEQUE",
- "paymentReference": "INV-1234",
- "currency": "ZAR",
- "amount": "1234.56"
}
}{- "statusCode": 422,
- "errorCode": "TRACKING:FLOW_NOT_FOUND",
- "title": "Semantic Error: (Unprocessable Entity) The request was well-formed but was unable to be followed due to semantic errors or business rule violations.",
- "detail": "The tracking object has mismatched flow IDs or sequence numbers",
- "instance": "0540fa0f-ccfa-4625-b006-725b4b580879",
- "extensions": [
- {
- "reason": "payerSpMessageSeq is out of sequence"
}
]
}Use the 'Loyalty Reply' endpoint to respond to a Loyalty Request logged to you by the Payee SP.
Use this endpoint to send a loyalty information reply from the Payer Service Provider to the Payee Service Provider during an open flow.
Request structure:
| kind required | string Approve an loyalty membership request Value: "APPROVE" |
required | object (ActivePrSpFlowTracker) An active Payer service provider tracking object exchanged during a flow |
| membershipReference required | string [ 1 .. 128 ] characters The membership reference of the Payer in the loyalty program. Todo: enrollment is not in scope for now, but the PrSP should have a mechanism to confirm the validity of this reference outside of this flow. |
{- "kind": "APPROVE",
- "tracking": {
- "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
- "payerSpMessageSeq": 1,
- "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
- "payeeSpMessageSeq": 1,
- "sessionInfo": {
- "status": "OPEN",
- "asAt": "2023-10-05T14:48:00.000Z",
- "expiresAt": "2023-10-05T14:48:00.000Z",
- "createdAt": "2023-10-05T14:48:00.000Z"
}
}, - "membershipReference": "PHE-184638"
}{- "statusCode": 422,
- "errorCode": "TRACKING:FLOW_NOT_FOUND",
- "title": "Semantic Error: (Unprocessable Entity) The request was well-formed but was unable to be followed due to semantic errors or business rule violations.",
- "detail": "The tracking object has mismatched flow IDs or sequence numbers",
- "instance": "0540fa0f-ccfa-4625-b006-725b4b580879",
- "extensions": [
- {
- "reason": "payerSpMessageSeq is out of sequence"
}
]
}Use the 'Identity Reply' endpoint to respond to a Identity Request logged to you by the Payee SP.
Use this endpoint to send an identity information reply from the Payer Service Provider to the Payee Service Provider during an open flow.
Request structure:
| kind required | string Approve an Identity request and provide Payer identification Value: "APPROVE" |
required | object (ActivePrSpFlowTracker) An active Payer service provider tracking object exchanged during a flow |
required | any |
{- "kind": "APPROVE",
- "tracking": {
- "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
- "payerSpMessageSeq": 1,
- "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
- "payeeSpMessageSeq": 1,
- "sessionInfo": {
- "status": "OPEN",
- "asAt": "2023-10-05T14:48:00.000Z",
- "expiresAt": "2023-10-05T14:48:00.000Z",
- "createdAt": "2023-10-05T14:48:00.000Z"
}
}, - "identification": {
- "kind": "S_DFID",
- "data": "string"
}
}{- "statusCode": 422,
- "errorCode": "TRACKING:FLOW_NOT_FOUND",
- "title": "Semantic Error: (Unprocessable Entity) The request was well-formed but was unable to be followed due to semantic errors or business rule violations.",
- "detail": "The tracking object has mismatched flow IDs or sequence numbers",
- "instance": "0540fa0f-ccfa-4625-b006-725b4b580879",
- "extensions": [
- {
- "reason": "payerSpMessageSeq is out of sequence"
}
]
}OPEN, EXPIRED or TERMINATED with both EXPIRED and TERMINATED being considered closed. The PrSP processes PeSP Requests only while the flow session status is OPEN.expiresAt timestamp sent by the PrSP to the PeSP in the tracking object of every interaction./request-session-extension endpoint)TERMINATED or had EXPIRED, the PrSP must return the final flow status with the appropriate error to all subsequent Requests for at least 240 minutes after the final status change. Thereafter the session context may be purged from the system (see Flow Record section for retention requirements).expiresAt timestamp to manage its activity but must not consider a session closed until formal confirmation to the fact is received from the PrSP.All QR+ Service Providers MUST maintain a record of flow interactions for compliance, traceability, and operational purposes. This record also serves as the foundation for Retry idempotency (see Retry and Idempotency section).
Core Principle: All SP interactions linked to a flow MUST be captured as part of the Flow Record. This includes all ingress payloads (incoming requests and responses) and egress payloads (outgoing requests, responses and replies). All interactions, where a valid flow context exists, MUST be included in a Flow Interaction Record while Interactions (responses) where an Error is returned without a firm flow context being established (e.g. Unauthorized, Forbidden, Flow sequence violations etc.) should not be included .
When exposing or presenting a Flow Record, its structure must comply to the FlowInteractionRecord schema defined below. This allows consistent formatting by all flow participants and easy comparison of Records between PrSP and PeSP.
const FlowInteractionRecord = z.object({
payerSpFlowId: PayerSpFlowId, // Mandatory PrSP Flow ID
payeeSpFlowId: PayeeSpFlowId, // Mandatory PeSP Flow ID
extractedAt: TimeStampRo, // Instant when record was extracted (flow may still be in progress)
flowStatus: z.enum(['OPEN', 'EXPIRED', 'TERMINATED']), // Flow status at instant of extraction
interactions: z.array(z.discriminatedUnion('reqOrRes', [
z
.object({
reqOrRes: z.literal('REQUEST'), // Request (from Http perspective)
authCtx: z.object({ // JWT Authorization context
iss: AuthzAuthorityId, // JWT issuer
sub: ServiceId, // JWT source service (subscriber)
aud: ServiceId, // JWT target service (audience)
}),
payeeSpMessageSeq: TrackingMessageSeq.optional(), // payeeSpMessageSeq if present in tracking object
payerSpMessageSeq: TrackingMessageSeq.optional(), // payerSpMessageSeq if present in tracking object
loggedAt: TimeStamp,
sourceServiceId: ServiceId, // The service which was the source of the request
targetServiceId: ServiceId, // The service which was the target/destination of the request
direction: z.enum(['PeSP<=PrSP', 'PeSP=>PrSP']), // Direction of the request
payload: z.discriminatedUnion('operation', [ // Any request payload from any Payer or Payee SP API as defined by API schemas
...PayeeProviderInteractionRequests.options,
...PayerProviderInteractionRequests.options,
]),
})
z
.object({
reqOrRes: z.literal('RESPONSE'), // Response (from Http perspective)
payeeSpMessageSeq: TrackingMessageSeq.optional(), // payeeSpMessageSeq if present in tracking object
payerSpMessageSeq: TrackingMessageSeq.optional(), // payerSpMessageSeq if present in tracking object
loggedAt: TimeStampRo,
sourceServiceId: ServiceId, // The service which was the source of the response
targetServiceId: ServiceId, // The service which was the target/destination of the response
direction: z.enum(['PeSP<=PrSP', 'PeSP=>PrSP']), // Direction of the request
payload: z.discriminatedUnion('operation', [ // Any response from any Payer or Payee SP API as defined by API schemas (includes http status value)
...PayeeProviderInteractionResponses.options,
...PayerProviderInteractionResponses.options,
]),
})
])),
})
Flow Interaction Records MUST be retained for at least five years. Flow Interaction Records serve as an audit record and MUST comply to relevant standards and practices.
QR+ APIs are designed for high-reliability, low-latency server-to-server communication in a distributed peer-to-peer environment. Both PrSP and PeSP implementations must handle transient failures gracefully through Retry mechanisms while preventing duplicate processing through idempotency guarantees.
In the context of this specification, a Retry is defined as an attempt by a client to complete a network exchange that did not receive an official response from the server. This should be seen in contrast to a Re-execute which is an attempt by the client to have the server re-evaluate a previously declined request.
The client, when executing a Retry, MUST use the same tracking sequences it used in the original request. When the intent is to do a Re-execute, it MUST use a different tracking sequence with a value larger than numbers previously used within the Flow context. The server will return a response to a Retry only if the new request is considered equivalent to that originally received. (See server Idempotency requirements)
A client MAY execute a Retry on any timeout condition or Error response but SHOULD limit Retries to errors consistent with communication failures. Retries MUST NOT be captured in in Flow Record to avoid duplicate entries against a specific flow sequence.
Timeout Configuration:
Circuit Breaker Policy:
Clients MUST implement circuit breaker protection to prevent cascading failures and enable fast-fail behavior
Circuit breaker states:
Circuit breaker configuration:
When a probe request during open state receives HTTP 503 with a Retry-After header, the circuit breaker SHOULD wait for the indicated duration before the next probe (overriding the 3-second default)
Circuit breaker state SHOULD be tracked per target service endpoint to isolate failures
Circuit breaker measurement window stats MUST be cleared when circuit breaker opens to avoid flapping
Retry Strategy:
Retry-After header, clients SHOULD wait for the indicated duration before attempting a RetryIdempotency for Retries:
Servers MUST use the Flow Record as the foundation for Retry idempotency. The Flow Record serves as both a compliance record and a response cache for safe Retry handling. When a request is received which is found to be Equivalent to an interaction on the Flow Record, then the corresponding response on the Record MUST be returned.
Request Equivalence:
Servers determine request equivalence using the following criteria (all must match):
sub) and audience (aud) claims match. Retries from different issuer or with different keys are allowed to accommodate token expiryIdempotency Detection and Processing:
When a request arrives, servers MUST:
TRACKING:FLOW_SEQUENCE_VIOLATION error responseTRACKING:FLOW_SEQUENCE_VIOLATIONCached responses must be available for return for a period of 240 minutes after the original request was processed. Sequence violations MUST NOT be logged to the Flow Record because they are request integrity failures, not legitimate Flow Interactions.
Handling Retries During Request Processing:
When a Retry arrives while the original request is still being processed, the Interaction Record entry will exist (request logged) but be incomplete (response not yet logged). In this scenario, servers MUST abandon the request and not send a response. Because the system cannot certify the outcome of the in-flight original request, the fail-safe approach is to trigger a client timeout for future Retry.
Session Expiry Interaction:
Server Error Response Guidance:
Retry-After header with 503 responses indicating recovery time in secondsThis API uses a JWT based (RFC7519), http bearer token security scheme. The JWT MUST be signed by an authorization authority registered to the QR+ Registry against a Service Provider profile. The API user identity is derived from the JWT issuer, key id and subject claim.
The JWT MUST contain the following Claims:
iss : Issuer Claim containing the authzAuthorityId as allocated by the QR+ Registrysub : Subject Claim containing either:aud : Audience Claim containing the service id of the service this token was created to accessexp : Expiration time Claim defining a time of expiry for this tokenpermissions : An array of token permissions for authorizations from:enrich - Can execute enrichment flows against the SP APIsThe JWT header must contain the following parameters:
alg : The JOSE algorithm used for JWT generationkid : The key id of the authorization authority key used to generate the JWTImplementers of this API must satisfy the following minimum requirements when validating a presented JWT:
sub) only when (i) the Issuer claim (iss) matches an Authorization Authority for this service (linked by the Registry) and (ii) The key id of the key used matches a key id used by the authorization authority (kid header parameter)alg property in the header is set to 'none' or if it does not align with the key type of the referenced Authorization Authorityaud Claim MUST match the service id of the service being accessed.| Term | Description |
|---|---|
| Acceptance Option | A payload specifying a payment rail supported by a Payee for payment. (Option Payload) |
| Anchor Domain | The common domain used for all Paylink web URIs, allowing deep linking through officially registered mobile applications. |
| Authorization Authority | An entity, managed on the QR+ Registry, which represents a Service Provider IdP that issues bearer tokens to authenticate API calls. A Service Provider manages Authorization Authorities according to their governance and security policies. |
| Extended Number Encoded Paylink | A 27-character alphanumeric Paylink format (ZA-FTI-SPII-PLV) optimized for Code128 barcode scanning with extended PLV namespace for medium lifespan experiences. |
| Facilitated Experience | The experience where the Scanner uses a Paylink Facilitator Application to start a QR+ Flow. |
| Flow | A Flow is the set of actions executed between a PrSP and PeSP as part of one QR+ session. |
| Flow Record | An audit trail record, created by both PrSP and PeSP, capturing all Service Provider Interactions exchanged as part of a unique Flow. |
| Flow Session | A time-bound session that tracks the lifecycle of a Flow with states OPEN, TERMINATED, or EXPIRED. A PrSP only process PeSP Requests while a Flow Session is OPEN. |
| Flow Tracker | A structured object within Service Provider Interactions containing Flow identifiers and Message Sequence Numbers to coordinate and track messages within a Flow. |
| Flow Type | A classification of QR+ Flows which govern use case requirements, Paylink lifespan constraints, supported encodings, and security requirements (e.g., pep-general, pep-shortcode, prp-connected, prp-disconnected). Service Providers are licensed for individual Flow Types. |
| Flow Type Indicator | An encoding-specific identifier that indicates which Flow Type is being used (e.g., 'general' representing the pep-general Flow Type in URI encoded Paylinks and '1' representing the prp-shortcode Flow TYpe in number encoded Paylinks). |
| Identity Request | A QR+ Request for Payer identity information initiated by the PeSP on behalf of the Payee. |
| Loyalty Request | A QR+ Request for Payer loyalty membership information initiated by the PeSP on behalf of the Payee. |
| Message Sequence Number | An incrementing counter maintained by each Service Provider to track the ordering and uniqueness of messages sent within a Flow. Used for audit, idempotency and replay detection. |
| Number Encoded Paylink | A 13-digit numeric Paylink format (FTI-SPII-PLV-Luhn) optimized for human reproduction via voice or manual keypad entry with built-in error detection. |
| Payee (actor) | A party who interacts with a Paylink with the intention of receiving payment. The Payee can be either a Scanner or a Presenter depending on the use case. |
| Payee Service Provider (PeSP) | A registered organization that executes QR+ enrichment flows on behalf of a Payee. The PeSP may deploy Paylink Provider and/or Paylink Facilitator functionality depending on its supported experiences. |
| Payer (actor) | A party who interacts with a Paylink with intention to pay. The Payer can be either a Scanner or a Presenter depending on the use case. |
| Payer Service Provider (PrSP) | A registered organization that executes QR+ enrichment flows on behalf of a Payer. The PrSP may deploy Paylink Provider and/or Paylink Facilitator functionality depending on its supported experiences. |
| Paylink | A structured identifier, encoded and presented in various formats, used by Payers and Payees to initiate a financial interaction. |
| Paylink actioning | The Scanner scanning the Paylink and submitting it to its Service Provider for processing. |
| Paylink Encoding Format | A Paylink Encoding Format is a specific way the Paylink attributes are encoded. For example, URI encoded format. |
| Paylink Facilitator (role) | A PrSP or PeSP organization registered to process Paylinks for Scanners. |
| Paylink generation | An exchange between the Presenter (Payee or Payer) and the Service Provider (PeSP or PrSP) to generate a new Paylink. |
| Paylink presentment | The Presenter presenting the Paylink to the Scanner for scanning. |
| Paylink Presentment Format | A Paylink presentment format specifies the technology used to present the Paylink for scanning. For example, QR code, Barcode and web click. |
| Paylink Provider (role) | A PrSP or PeSP registered to create Paylinks for Presenters. |
| Paylink Quarantine Period | The period, after a Paylink has been cancelled, during which the PLV must not be used for a new Paylink. |
| Paylink resolution | The Paylink Facilitator presenting the Paylink to the Paylink Provider to initiate a QR+ flow. |
| Paylink Value (PLV) | The cryptographically secure random identifier component within a Paylink that uniquely identifies a specific Paylink instance. |
| Payment Confirmation | A rail-specific payload passed by the PrSP to the PeSP confirming successful initiation of a Push Payment, without claiming settlement status. |
| Payment Conclusion | A rail-specific payload passed by the PeSP to the PrSP to provide its view on the outcome of a Payment Request. |
| Payment Delegation | A rail-specific payload passed by the PrSP to the PeSP containing Pull Payment credentials and transaction details, delegating payment initiation to the PeSP. |
| Payment Request | A QR+ Request for payment initiated by the PeSP on behalf of the Payee. |
| Presenter (role) | A Payer or Payee who presents a Paylink to a Scanner party to action. The Presenter interacts with her Service Provider to create the Paylink according to her requirements. |
| Pull Payment | A payment mechanism that is initiated by a PeSP. |
| Push Payment | A payment mechanism that is initiated by a PrSP. |
| QR+ Flow Orchestration | The process of coordinating actions between the PrSP, PeSP, Payer and Payee to achieve a successful transaction outcome. |
| QR+ Registry Service | A service operated by SARB NPU to enable service discovery of registered Service Providers and Services. |
| QR+ Service Provider | An organization registered to take part in QR+ flows on behalf of Payers and/or Payees. |
| Scanner (role) | A party who actions a Paylink by submitting it to their chosen Paylink Facilitator. |
| Service Provider Indicator | An encoding-specific identifier, assigned by SARB to a registered Service Provider, which allows all Service Providers to identify them as the creator of a Paylink. |
| Service Provider Interaction | A request and response exchange between a PrSP and a PeSP during a Flow, captured for audit trail, Flow analysis, and idempotency purposes. |
| Signed CBOR Encoded Paylink | A cryptographically signed Concise Binary Object Representation (CBOR) Paylink format using ECDSA signatures for high-security use cases requiring cryptographic attestation. |
| URI Encoded Paylink | A non-URL, RFC3986-compliant URI format for Paylinks (qr-plus://<anchor domain>/v1/<flowTypeIndicator>/<spIndicator>/<plv>) supporting deep linking and extended lifespans. |