Unified Transfer is in active development. As a pioneer merchant, you will be informed of the updates through your Maya Relationship Manager.
Overview
API authentication in Unified Transfer ensures that only authorized partners can initiate fund transfers. It protects sensitive financial data and enforces strong, financial-grade security controls.
By the end of this guide, you will understand the multi-layered security architecture in Unified Transfer.
Authentication Architecture
Maya's Unified Transfer follows a layered security approach aligned with OAuth 2.0 and FAPI standards:
-
OAuth 2.0 Client Credentials Grant: Machine-to-machine authentication and authorization. Clients obtain an access token from Maya Connect and present it as a Bearer token when calling protected APIs.
-
FAPI-Compliant Request Signing: All protected API requests must be digitally signed using JWS (JSON Web Signature) with the client's private key to ensure message integrity, non-repudiation, and protection against tampering or replay attacks.
This combination meets financial industry security standards and BSP-aligned requirements.
For Unified Transfer, every protected API request requires both:
- A valid OAuth 2.0 Bearer token
- A valid JWS signature of the request body
Why Request Signing Is Required
OAuth 2.0 access tokens authenticate the client while JWS request signing provides:
- Integrity: Ensures the request body was not tampered with or modified in transit
- Non-repudiation: Cryptographic proof the request came from you
- Enhanced Security: Additional layer beyond OAuth 2.0
Per-Request Authentication in Unified Transfer
Each API request must follow this sequence.
Step 1: Generate or Reuse a Valid Access Token
- If your cached access token is still valid, reuse it
- If it has already expired, generate a new one
See the Bearer Authentication for the steps on generating an access token.
Step 2: Prepare the Exact Request Body
Create the request body according to the documented API specifications.
- Serialize the JSON body exactly as it will be sent
- Do not modify formatting after signing
- The signature must match the exact byte representation
For requests that has no request body, sign the empty body
Step 3: Generate JWS Signature (Per Request)
Each API request must have a unique JWS signature, even if using the same access token.
Construct and generate the JWS signature for the exact request body. For complete guidance, see JWS Implementation Guide for Unified Transfer.
Step 4: Send the Authenticated/Signed Request
Include both the access token and the JWS signature in your request headers:
POST /v1/transfers/p2p HTTP/1.1
Host: sandbox.api.maya.ph
Authorization: Bearer eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ...
x-jws-signature: eyJhbGciOiJSUzI1NiIsImtpZCI6InlvdXItdW5pcXVlLWtleS1pZC0xMjM0NSIsInR5cCI6IkpXVCJ9...
x-idempotency-key: 3ebc4615-d8a1-468b-b72c-fb71ff6c5d03
x-originator-transaction-id: TXN-2025-001234
Content-Type: application/json
{
"data": {
"initiation": { ... }
}
}
Both authentication mechanisms are validated in Maya before the request is processed.
Complete Example Flow:
// Step 1: Get access token (reuse for multiple requests)
const accessToken = await getAccessToken();
// Step 2: Sign the request payload (unique for each request)
const requestBody = { data: { initiation: { /* ... */ } } };
const signature = jwt.sign(requestBody, privateKey, {
algorithm: 'RS256',
keyid: 'your-key-id'
});
// Step 3: Send the request
const response = await fetch('https://sandbox.api.maya.ph/v1/transfers/p2p', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-jws-signature': signature,
'x-idempotency-key': generateUUID(),
'x-originator-transaction-id': 'TXN-2025-001234',
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
Request Headers
Mandatory Headers
All API requests must include:
| Header | Description | Example |
|---|---|---|
Authorization | Bearer token obtained via OAuth 2.0 | Bearer eyJraWQiOiJyc2ExIi... |
x-jws-signature | Detached JWS signature of request body | eyJhbGciOiJSUzI1NiIsIm... |
x-idempotency-key | UUID for idempotency (Initiate Transfer only) | 3ebc4615-d8a1-468b-b72c-fb71ff6c5d03 |
x-originator-transaction-id | Your unique transaction identifier | TXN-2025-001234 |
Optional FAPI Headers
These headers provide additional context and traceability:
| Header | Description | Format | Example |
|---|---|---|---|
x-fapi-interaction-id | RFC4122 UUID for correlation | UUID | 52fc19ea-71b1-45b4-8cbe-eacb7d9670a6 |
x-fapi-auth-date | Last authentication timestamp | RFC 7231 Full Date | Fri, 27 Jun 2025 04:14:02 UTC |
x-fapi-customer-ip-address | Client IP address | IPv4/IPv6 | 192.168.1.1 |
x-customer-user-agent | Client user agent string | String | MyApp/1.0.0 |
FAQs
Q: Do I need to sign every API request?
A: Yes. All requests to the Unified Transfer must include a valid JWS signature in the x-jws-signature header.
Q: Can I use the same access token for multiple requests?
A: Yes, you can reuse the same access token until it expires (typically 1 hour). However, each request must have its own unique JWS signature.
Q: What happens if my JWKS endpoint goes down?
A: Maya will be unable to verify your request signatures, and all API requests will fail with a 403 error. Ensure your JWKS endpoint has high availability.
Q: How do I test authentication in Sandbox?
A: Use the Sandbox OAuth endpoints and your Sandbox credentials. The authentication flow is identical to Production, but uses separate credentials and endpoints.
Next Steps
Now that you understand how API authentication works in Unified Transfer, you're ready to continue with: