Pine Labs Payment Protocol - Quickstart
Create agent-native payment experiences where AI agents securely discover, authorize, and complete paid API requests through a standardized machine payment protocol.
Build one paid endpoint first. The Server SDK protects the route and returns 402 until the Client retries with a payment credential. The Client SDK handles challenge parsing, token creation, retry, and receipt parsing.
Supported Payment Methods
1. One Time Mandate (Available now) - Customer uses OTM as the payment authorization, and each paid request completes through an OTM token and server-side capture.
2. UPI ReservePay (Available now) - Customer approves a mandate, and each paid request debits the approved reserve.
3. Cards (Available now) - P3P challenge and receipt remain unchanged; merchants must support card pre-authorization with subsequent captures executed against the pre-authorization.
4. Stablecoin (Future scope) - P3P challenge and receipt stay the same; wallet, signature, and settlement behavior become rail-specific.
Payment Lifecycle
Setup Guide
Follow the below steps to integrate with Pine Labs payment protocol for UPI and Cards-- Configure Credentials
- Setup Customer Mandate
- Fetch Mandate Balance for UPI
- Create a Paid Server Endpoint
- Call The Paid Resource
- Verify The Result
- Go Live
Prerequisites — Get Your API Credentials
Before you begin integrating, create your free Pine Labs Online developer account to get access to UAT credentials, API keys, and the merchant dashboard.
- Sign Up — Visit the Pine Labs Online Dashboard and register with your business email.
Create Account - Verify Your Email — Confirm your email address through the verification link sent to your inbox.
- Find Your Credentials — Once logged in, Switch to Test mode and navigate to Settings → API Keys to generate your test-mode API key and secret.
- Copy both credentials — These are your PINELABS_CLIENT_ID and PINELABS_CLIENT_SECRET values. Store them securely — you will need them in the next step.
Do not expose PINELABS_CLIENT_SECRET in browser code. Use them only in backend services, agents, or server-side runtimes.
📘 Note: Need help with integration?
Reach out to our integration support team at pgintegration@pinelabs.com
1. Configure Credentials
Set these environment variables in the backend runtime that owns the SDK calls:
PINELABS_CLIENT_ID="<<PINELABS_CLIENT_ID>>"
PINELABS_CLIENT_SECRET="<<PINELABS_CLIENT_SECRET>>"
PINELABS_CLIENT_ID and PINELABS_CLIENT_SECRET come from Pine Labs after merchant onboarding. Use sandbox credentials with P3PEnvironment.SANDBOX and production credentials with P3PEnvironment.PRODUCTION.
The server SDK derives the local P3P challenge HMAC key from PINELABS_CLIENT_SECRET internally. There is no separate challenge-signing value to configure or share.
Complete these steps before adding Grantex to your integration:
Prerequisite
Complete these steps before adding Grantex to your integration:
Go to Grantex portal and sign in.
If you are a new user, create an account by visiting the Grantex Sign Up page.
Create account in Grantex using your Org Name and Email ID. After Account creation you will get the API Key.
📘 Note: Save your API key — you won't see it again.
Add mpp:payment:initiate and mpp:payment:max_txn_paise:* inside the scope to create the Agent.
Use this API key and Agent ID when initializing the P3P SDK.
2. Setup Customer Mandate
For the current UPI ReservePay rail, the customer must have an active mandate before paid requests can complete. Create that mandate with the server SDK before the client starts calling paid routes.
import {
Amount,
PaymentMethod,
PineLabsOnlineP3P
} from "p3p-server-sdk";
const p3p = PineLabsOnlineP3P.create(config);
const mandate = await p3p.createMandate({
customerReference: "customer-ref-123",
mobileNumber: "98******10",
amount: new Amount(100000, "INR"),
paymentMethod: PaymentMethod.RESERVE_PAY
// For Cards use paymentMethod: PaymentMethod.CARD
// For UPI OTM use paymentMethod: PaymentMethod.OTM
});
The amount uses paise. 100000 means Rs 1,000.
Activation steps for UPI:
The createMandate response includes a deep_link field — a UPI intent URL that the customer must approve in their UPI app. Follow these steps:
- Extract deep_link— Read deep_link from the create mandate response.
- Generate a QR code — Use any QR library (e.g. qrcode in Python, qrcode.react in JS) to encode the deep_link into a scannable QR.
- Display to customer — Show the QR code on your UI. The customer scans it with their UPI app to approve the mandate.
- Poll for activation — After the customer scans, poll the mandate status until it becomes ACTIVE.
Activation steps for Cards:
- Get checkout URL — Extract the checkout_url from the create mandate response.
- Display checkout — Show the checkout URL in a modal/iframe on your UI. The customer enters their card details in the checkout window.
- Card validation — The customer completes card authorization. After successful authorization, they validate the transaction using OTP sent to their registered mobile.
- Poll for activation — After the user completes payment and authorization is successful, poll the mandate status until it becomes ACTIVE.
3. Fetch Mandate Balance for UPI
After the customer approves a mandate, your backend can fetch the current blocked, debited, and remaining balance before starting or retrying paid calls.
const balance = await p3p.getMandateBalance({
authorizationId: "auth_123",
phoneNumber: "98******10",
paymentMethod: PaymentMethod.RESERVE_PAY
});
console.log({
status: balance.status,
blocked: balance.amount?.value ?? 0,
debited: balance.balance_details?.amount_debited.value ?? 0,
remaining: balance.balance_details?.amount_remaining.value ?? 0
});
Use getMandateBalance / get_mandate_balance to fetch the mandate balance via GET /mpp/v1/balance.
Currently, only PaymentMethod.RESERVE_PAY are supported.
While integrating with PaymentMethod.OTM and PaymentMethod.CARD, skip the Fetch Mandate Balance step.
4. Create a Paid Server Endpoint
import {
Amount,
ChargeOptions,
P3PEnvironment,
PaymentGateway,
PaymentMethod,
decidePayment
} from "p3p-server-sdk";
const config = {
clientId: process.env.PINELABS_CLIENT_ID!,
clientSecret: process.env.PINELABS_CLIENT_SECRET!,
paymentGateway: PaymentGateway.PineLabsOnline,
availablePaymentMethods: [PaymentMethod.RESERVE_PAY, PaymentMethod.OTM, PaymentMethod.CARD],
env: P3PEnvironment.SANDBOX,
realm: P3PEnvironment.SANDBOX,
grantex: {
enforceGrant: true,
agentId: process.env.GRANTEX_AGENT_ID!,
issuer: process.env.GRANTEX_ISSUER ?? "https://grantex.dev",
requiredScopes: ["mpp:payment:initiate","mpp:payment:max_txn_paise:order_amount_in_paisa"],
hosted: {
apiKey: process.env.GRANTEX_API_KEY!,
baseUrl: process.env.GRANTEX_BASE_URL ?? "https://api.grantex.dev"
}
}
};
export async function GET(request: Request) {
const decision = await decidePayment({
credentialHeader: request.headers.get("P3P-Credential") ?? undefined,
grantexTokenHeader: request.headers.get("X-Grantex-Token") ?? undefined,
config,
chargeOptions: new ChargeOptions(
new Amount(10000, "INR"),
"/api/weather"
)
});
if (decision.action !== "proceed") {
return Response.json(decision.problemDetails, {
status: decision.status,
headers: decision.headers
});
}
return Response.json(
{ forecast: "Clear", paid: true },
{ headers: decision.headers }
);
}
decidePayment() wraps any route with the full challenge → verify → capture cycle. Define the price and resource path — the SDK handles the rest.
- config — Merchant credentials + environment
- chargeOptions — Price and resource path
- credentialHeader — Reads the P3P-Credential header (undefined on first call)
- decision.action !== "proceed" — Returns 402 challenge if credential missing/invalid
- action === "proceed" — Payment captured, return resource + receipt headers
5. Call The Paid Resource
import {
P3PEnvironment,
PaymentMethod,
PineLabsOnlineClient
} from "p3p-client-sdk";
const client = PineLabsOnlineClient.create({
selectedPaymentMethod: PaymentMethod.RESERVE_PAY,
// For Cards use selectedPaymentMethod: PaymentMethod.CARD
// For UPI OTM use selectedPaymentMethod: PaymentMethod.OTM
clientId: process.env.PINELABS_CLIENT_ID!,
clientSecret: process.env.PINELABS_CLIENT_SECRET!,
env: P3PEnvironment.SANDBOX
});
const response = await client.get(
"https://server.example.com/api/weather",
{},
{
customerReference: "customer-ref-123",
mobileNumber: "98******10",
grantexToken: storedGrant.grantToken
}
);
const receipt = response.headers.get("Payment-Receipt");
If the first request receives 402, the client SDK decodes the challenge, creates a one-time token, retries with P3P-Credential: Payment, and returns the final server response. Use client credentials in trusted backends or agents, and switch to customer-key mode when your integration is built around customer API tokens.
For OTM paid calls, set selectedPaymentMethod: PaymentMethod.OTM / selectedPaymentMethod=PaymentMethod.OTM and ensure the server challenge advertises PaymentMethod.OTM.
For CARD paid calls, set selectedPaymentMethod: PaymentMethod.CARD / selectedPaymentMethod=PaymentMethod.CARD and ensure the server challenge advertises PaymentMethod.CARD.
6. Verify The Result
An unpaid request must return:
HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment <challenge>
Content-Type: application/problem+json
Cache-Control: no-store
A paid request must return the protected response and Payment-Receipt. Store the receipt with your application-level order, usage, or audit record.
7. Go Live
Before production:
- Complete Pine Labs merchant onboarding.
- Move from sandbox credentials to production credentials.
- Confirm
PINELABS_CLIENT_SECRETis stored securely because it also derives the local challenge HMAC key. - Confirm mandate limits, refund behavior, and receipt storage with your Pine Labs integration owner.
What's Next
Use UPI Reserve Pay (SBMD)
Integrate Single Block Multi Debit (SBMD) mandates for recurring and merchant-initiated payment experiences.
View Docs →