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.
POST /par to PN with client_id and authorization_details.POST /consents/events to DP with consent_id and dc_id. DP verifies JWS signature.request_uri to DC.PN /authorize with client_id and request_uri./authorize with OIDC params: response_type=code, client_id, redirect_uri, scope (openid), state, nonce, consent_id, signature.PATCH /consents/{consent_id} at PN with status, user_identity, accounts (id_token no longer included).redirect_uri with code and state query parameters.POST /v1/oauth/token at DP with grant_type=authorization_code, code, redirect_uri, and private_key_jwt client auth.id_token and access_token.id_token: verifies signature via DP JWKS, checks issuer, audience, expiry, nonce. Cross-references hashed_id_number with consent update.redirect_uri with authorization_code.POST /consents/{consent_id}/revoke to PN.POST /consents/events to DC.JWS Detached Object Signing with algorithms: PS256, ES384, ES512.
Protected headers include: alg, kid, iss, jti, iat, crit. The iat is validated within a 5-minute window.
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.
Resource data (Account, Balances, Transactions) is encrypted with DC public cert using RSA-OAEP-256 / A256GCM. Responses are JWE inside JWS.
PN Signature Cert — PN signs request payloads. JWK use = sig.
DC Signature Cert — DC signs request payloads. JWK use = sig.
DP Signature Cert — DP signs request payloads. JWK use = sig.
DC Encryption Cert — DP encrypts data with DC cert. JWK use = enc.
JWKS responses should be cached for a maximum of 15 minutes. During key rotation, old and new keys coexist with different kid values.
Used by PN when calling the DP Token Endpoint. The client_assertion JWS body includes:
| Parameter | Req | Type | Description |
|---|---|---|---|
iss |
R | String(36) | Client ID (PayNet OFP). |
sub |
R | String(36) | Client ID (PayNet OFP). |
aud |
R | String(300) | DP Token Endpoint URL or issuer value. |
nbf |
O | Integer | Not-before Unix timestamp. |
exp |
R | Integer | Expiry Unix timestamp (max 10 min). |
iat |
R | Integer | Issued-at Unix timestamp. |
jti |
R | String(36) | Unique JWT ID for replay detection. |
| 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."
}
The Data Provider exposes an OAuth 2.0 / OpenID Connect Authorization Server (AS) for consent and token flows. Only the endpoints listed below are exposed on the public ingress.
For the mock DP environment the AS issuer is https://api.dp-as.openfinance.dev.inet.paynet.my/.
Start from the issuer URL
The AS base URL (issuer) for this environment is https://api.dp-as.openfinance.dev.inet.paynet.my/. The issuer 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.dp-as.openfinance.dev.inet.paynet.my/.well-known/openid-configurationissuer, authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint, and other metadata. Use these URLs for all subsequent requests; do not hardcode paths.Use the URLs from the discovery response
Use the values from the discovery response for authorization, token exchange, UserInfo, and JWKS. Exact paths may differ by deployment; discovery is the source of truth.
Each of the following endpoints is exposed on the public ingress. Exact URLs are also provided in the discovery document.
| Method | Endpoint | Purpose | Specification / RFC |
|---|---|---|---|
GET |
/.well-known/openid-configuration |
Discovery / issuer metadata | OpenID Connect Discovery 1.0, RFC 8414 (OAuth 2.0 AS Metadata) |
GET |
/.well-known/jwks.json |
JSON Web Key Set (token validation) | RFC 7517 (JWK) |
GET |
/oauth2/auth |
Authorization (user consent) | RFC 6749 §3.1 (OAuth 2.0) |
POST |
/oauth2/token |
Token exchange | RFC 6749 §3.2 (OAuth 2.0) |
POST |
/oauth2/revoke |
Revoke access or refresh token | RFC 7009 (Token Revocation) |
GET |
/oauth2/sessions/logout |
OpenID Connect logout (front- and back-channel) | OpenID Connect Session Management, Back-channel |
GET |
/userinfo |
OpenID Connect UserInfo | OpenID Connect Core 1.0 (UserInfo) |
PayNet retrieves an account balances information from DP.
| account_id required | string AccountID is the account identifier (path). |
| consent_id | string ConsentID is the consent identifier (required, query or header). |
{- "status": "string",
- "error_message": "string",
- "data": "string"
}Description: PayNet notifies DP when consent is created or updated.
| event_type | string Default: "consent_event_type_unspecified" Enum: "consent_event_type_unspecified" "consent_created" "consent_updated" "consent_status_updated" EventType is the consent event type (PayNet section 13). |
object Data is the consent object. |
{- "event_type": "consent_event_type_unspecified",
- "data": {
- "consent_id": "string",
- "dc_id": "string",
- "dp_id": "string",
- "id_type": "id_type_unspecified",
- "hashed_id_number": "string",
- "consent_type": "consent_type_unspecified",
- "consent_purpose": "consent_purpose_unspecified",
- "permissions": [
- "consent_permission_unspecified"
], - "expiration_datetime": "2019-08-24T14:15:22Z",
- "status": "consent_status_unspecified",
- "status_reason": {
- "reason_code": "consent_status_reason_code_unspecified",
- "reason_description": "string"
}, - "account_access": {
- "accounts": [
- {
- "account_id": "string",
- "account_number": "string",
- "account_name": "string"
}
]
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "updated_by": "consent_updated_by_unspecified"
}
}{ }DP updates consent status and user info after the user authorizes the consent.
| consent_id required | string ConsentID is the consent identifier (from path parameter). |
| consent_id | string ConsentID is the consent identifier (from path parameter). |
| status | string Status is the consent status (32 chars, required). |
| id_token | string IDToken is the JWT issued by the Data Provider (3000 chars, required if status = Authorized). |
object UserIdentity is the user identity object (required if status = Authorized). | |
Array of objects Accounts is the list of consent account objects (required if status = Authorized). |
{- "consent_id": "string",
- "status": "string",
- "id_token": "string",
- "user_identity": {
- "id_type": "string",
- "masked_id_number": "string",
- "hashed_id_number": "string",
- "masked_mobile_number": "string",
- "hashed_mobile_number": "string"
}, - "accounts": [
- {
- "account_id": "string",
- "account_number": "string",
- "account_name": "string"
}
]
}{ }