Paynet OFP enables secure, consumer-consented access to financial data across Malaysian institutions — banks, fintechs, pension providers, insurers, and more. The platform connects Data Consumers (DCs) requesting user data with Data Providers (DPs) supplying it, mediated through Paynet's Authorization Server and Resource Server.
Manages consent, authentication, OAuth 2.0 tokens, and enforces FAPI 2.0 security.
Serves financial data — accounts, balances, transactions — behind valid access tokens.
The primary flow for obtaining user-consented access to financial data.
POST /par with client_id and signed JAR containing authorization_details.request_uri to DCGET /authorize with only client_id and request_uri.redirect_uri.POST /token with authorization_code grant, code_verifier (PKCE), and client auth.access_token, id_token, refresh_token, and authorization_details to the DC.When an access token expires, the DC uses the refresh token to obtain a new one without re-authenticating the user. Per FAPI 2.0, the AS does not rotate refresh tokens — the same refresh token remains valid across multiple access token renewals.
The DC obtains a client_credentials access token, then calls POST /consents/{consent_id}/revoke. OFP revokes the consent and notifies the DP via webhook.
The DC sends a GET request with the access_token and an x-signature JWS header. OFP introspects the token, verifies the signature, forwards to the DP. The DP encrypts the response data with the DC's public encryption key (JWE inside JWS) and returns it through OFP to the DC.
This specification conforms to the OpenID FAPI 2.0 Security Profile (Final, 19 Feb 2025). All authorization interactions between DCs and the Paynet OFP Authorization Server must follow these requirements.
All API connections require mutual TLS authentication. Both client and server present certificates, establishing bidirectional trust. TLS 1.2+ is mandatory with BCP 195 cipher suites.
Access tokens are bound to the client's mTLS certificate. When the DC presents an access token to a Resource Server, the RS introspects the token to obtain the certificate thumbprint (cnf.x5t#S256) and validates it matches the mTLS connection. Tokens MUST NOT be transmitted in query strings.
All authorization requests must go through the PAR endpoint (RFC 9126). The AS rejects any authorization request not initiated via PAR. The request_uri is valid for less than 600 seconds. Only client_id and request_uri may be sent to the /authorize endpoint.
Every authorization request must include a fresh, cryptographically random code_challenge using the S256 method. The plain method is prohibited. The code_verifier is validated at the token endpoint.
The AS includes the iss parameter in every authorization response. DCs MUST compare this against the issuer value from the .well-known metadata and reject mismatches.
Only asymmetric methods are permitted: private_key_jwt, tls_client_auth, or self_signed_tls_client_auth. Shared secret methods (client_secret_basic, client_secret_post, client_secret_jwt) are prohibited.
Refresh tokens MUST NOT be rotated. Per FAPI 2.0, rotation provides no security benefit with sender-constrained tokens and risks interoperability issues.
| Requirement | Detail |
|---|---|
| JWS Algorithms | PS256, ES256, EdDSA (Ed25519) only. RS256, none, etc. are prohibited. |
| JWE Algorithms | RSA-OAEP-256 (key encryption), A256GCM (content encryption). |
| RSA Key Size | ≥ 2048 bits. |
| EC Key Size | ≥ 160 bits effective security. P-256 and Ed25519 both qualify. |
| Credential Entropy | Authorization codes, access tokens, and refresh tokens must have ≥ 128 bits of entropy. |
| JWT Best Practices | RFC 8725 compliance: verify alg against allowlist, validate all claims, avoid x5u/jku headers. |
| TLS | TLS 1.2+ with BCP 195 cipher suites. HSTS on browser-facing endpoints. No CORS on /authorize. |
All JWS headers must include alg (PS256/ES256/EdDSA) and kid (x5t#S256 certificate thumbprint). Timestamps (nbf, iat, exp) are used for replay protection. The AS accepts JWTs with iat/nbf up to 10 seconds in the future but rejects those more than 60 seconds ahead.
For resource API requests, the DC signs request parameters into an x-signature header JWS. Resource API responses are returned as a JWE (encrypted with DC's public key) nested inside a JWS (signed with DP's private key).
| Code | Description |
|---|---|
200 |
OK |
201 |
Created |
204 |
No Content |
303 |
Redirect |
400 |
Bad Request |
401 |
Unauthorized |
403 |
Forbidden |
404 |
Not Found |
405 |
Method Not Allowed |
429 |
Too Many Requests |
500 |
Internal Server Error |
503 |
Service Unavailable |
Returned in error responses from authorization and token endpoints.
| Code | Description |
|---|---|
invalid_request |
Missing or invalid parameter. |
invalid_client |
Client authentication failed. |
invalid_grant |
Invalid, expired, or revoked grant/code. |
unauthorized_client |
Client not authorized for this grant type. |
unsupported_grant_type |
Grant type not supported. |
access_denied |
Resource owner or AS denied the request. |
invalid_scope |
Invalid or unknown scope. |
server_error |
Unexpected server-side condition. |
temporarily_unavailable |
Server overloaded or in maintenance. |
| Code | Description |
|---|---|
Consent.AccountTemporarilyBlocked |
Account temporarily blocked by DP business rule. |
Consent.PermanentAccountAccessFailure |
Account permanently unavailable. |
Consent.TransientAccountAccessFailure |
Account unavailable due to transient failure. |
AccessToken.InvalidScope |
Token does not have required scope. |
Consent.Invalid |
Consent is in an invalid state. |
Consent.BusinessRuleViolation |
DP business rule prevents the operation. |
JWS.InvalidSignature |
JWS signature verification failed. |
JWS.InvalidClaim |
One or more JWS claims failed validation. |
JWE.DecryptionError |
JWE decryption failure. |
{
"error": "invalid_grant",
"error_description": "The authorization code has expired."
}
When receiving HTTP 429 (Too Many Requests), implement retry with exponential backoff to avoid overwhelming the platform.
| Attempt | Wait Time |
|---|---|
| Initial | Immediate |
| Retry 1 | 5 seconds |
| Retry 2 | 10 seconds |
| Retry 3 | 20 seconds |
| Retry 4 | 40 seconds |
The PayNet Open Finance Platform exposes a FAPI 2.0–compliant Authorization Server (AS) for OAuth 2.0 and OpenID Connect flows. Integrators should use discovery to obtain all AS endpoints and metadata instead of hardcoding URLs.
For this environment the AS issuer is https://api.as.openfinance.dev.inet.paynet.my.
Start from the issuer URL
You will receive the AS base URL (issuer) for your environment (e.g. from the Partner Dashboard or onboarding). The issuer is the logical identifier of the AS and must match the iss claim in tokens and authorization responses.
Fetch the discovery document
Request the OpenID Connect / OAuth 2.0 metadata at:
https://api.as.openfinance.dev.inet.paynet.my/.well-known/openid-configurationissuer — the AS issuer URL (validate that it matches the iss in responses and tokens).authorization_endpoint, token_endpoint, jwks_uri, pushed_authorization_request_endpoint, userinfo_endpoint, and optionally introspection_endpoint, revocation_endpoint.Use the URLs from the discovery response
Use these values for all subsequent requests (authorization, token exchange, PAR, UserInfo, JWKS). Do not hardcode paths; environments may differ.
The following endpoint types are exposed by the AS. Exact URLs are provided in the discovery document; this list is for reference only.
| Purpose | Typical path (from discovery) | Specification / RFC |
|---|---|---|
| Discovery / issuer metadata | /.well-known/openid-configuration |
OpenID Connect Discovery 1.0, RFC 8414 (OAuth 2.0 AS Metadata) |
| Authorization (user consent) | e.g. /authorize |
RFC 6749 §3.1 (OAuth 2.0) |
| Token exchange | e.g. /token |
RFC 6749 §3.2 (OAuth 2.0) |
| Pushed Authorization Request (PAR) | e.g. /par |
RFC 9126 (PAR) |
| UserInfo | e.g. /userinfo |
OpenID Connect Core 1.0 (UserInfo) |
| JSON Web Key Set (JWKS) | from jwks_uri in discovery |
RFC 7517 (JWK), used for token validation |
The AS follows the FAPI 2.0 Security Profile (e.g. authorization code flow, PKCE S256, PAR where required). Behaviour and required parameters are defined by FAPI 2.0, OAuth 2.0, and OpenID Connect; use the discovery document and those specifications as the source of truth.
To reduce integration effort and avoid common mistakes, use a client library or product that is FAPI 2.0–compliant (or certified). The OpenID Foundation provides conformance tests and certification for both Authorization Servers and OAuth/OIDC clients. For certified implementations and test tools, see:
Choosing a client that has passed FAPI 2.0 conformance tests (or is listed as certified) helps ensure correct use of PAR, PKCE, and other security requirements of the platform.
DC retrieves an account balances information from DP via PayNet.
| account_id required | string AccountID is the unique identifier for the account. |
| consent_id | string ConsentID is the consent identifier for authorization. |
{- "data": "string"
}DC retrieves an existing consent using the consent ID. The DC may also use this endpoint to obtain the list of accounts associated with that consent.
| consent_id required | string ConsentID is the unique consent identifier (path parameter). |
{- "data": {
- "consent_id": "string",
- "dc_id": "string",
- "dp_id": "string",
- "id_type": "string",
- "hashed_id_number": "string",
- "consent_type": "string",
- "consent_purpose": "string",
- "permissions": [
- "string"
], - "expiration_datetime": "string",
- "status": "string",
- "status_reason": {
- "reason_code": "string",
- "reason_description": "string"
}, - "accounts": [
- {
- "account_id": "string",
- "account_number": "string",
- "account_name": "string"
}
], - "created_at": "string",
- "updated_at": "string",
- "updated_by": "string"
}
}