Pine LabsDOCS

Integration Steps

Integrate UPI One-Time Mandate with OT direct-execution subscriptions, mandate registration, and presentation execution.

Use this guide to integrate UPI One-Time Mandate (OTM) using the OT direct-execution subscription flow. In this flow, you create a no-plan OT subscription, register the bank mandate through the Payments API, and then execute the debit through the Presentation API after the subscription becomes active.

Before you begin

Ask your Pine Labs Online account manager to enable UPI OTM for your merchant account and confirm your public base URLs, approved transaction limits, validity window, supported MCC, and webhook configuration.

Scope

OT direct execution is a no-plan subscription product:

  • No plan is created for the subscription.
  • execution_mode is DIRECT_EXECUTION.
  • plan_details.frequency is OT.
  • The subscription amount becomes the maximum amount available for the Presentation API.
  • The existing Presentation API request and response schema is used.
  • The bank mandate registration step is completed through the Payments API with request_type set to CREATE_MANDATE.

Integration flow

1
Ensure the customer exists

Create or reuse a Pine Labs Online customer profile and keep the customer_id available for subscription creation.

2
Create the OT subscription

Create a no-plan OT direct-execution subscription. The response returns a subscription_id, an order_id, and status as CREATED.

3
Register the mandate through Payments API

Use the returned order_id to create a UPI payment with mandate_info.request_type set to CREATE_MANDATE.

4
Wait for activation

Wait for the mandate callback or inquiry processing to move the subscription to ACTIVE.

5
Create presentation

Create a Presentation API request against the active subscription to execute the debit.

Authentication and headers

Subscription APIs

Include merchant context for subscription and presentation calls:

Http
Merchant-ID: <merchant-id> Authorization: Bearer <access-token> Content-Type: application/json Accept: application/json

Payments API

The Payments API call must use an access token generated by the Pine Labs authentication API.

Http
Authorization: Bearer <access-token> Content-Type: application/json Accept: application/json Request-ID: <uuid> Request-Timestamp: <utc-timestamp>
Backend-only integration

Keep client_id, client_secret, access tokens, and merchant identifiers only on backend systems. Do not call Pine Labs authentication, subscription, presentation, or payment APIs directly from a browser or frontend client.

1. Generate token

Generate an access token from your backend server and use it in subsequent API calls.

Bash
curl --request POST \ --url <base-url>/api/auth/v1/token \ --header 'Content-Type: application/json' \ --header 'Request-ID: <unique-request-id>' \ --header 'Request-Timestamp: <iso-8601-timestamp>' \ --data '{ "client_id": "<your-client-id>", "client_secret": "<your-client-secret>", "grant_type": "client_credentials" }'
Keep credentials server-side

Do not store API credentials, access tokens, or client secrets in frontend code, mobile apps, logs, screenshots, or public repositories.

2. Create a customer

Create or reuse a Pine Labs Online customer profile before initiating the mandate. Store the returned customer_id against your user profile.

Bash
curl --request POST \ --url <base-url>/api/v1/customer \ --header 'Authorization: Bearer <access-token>' \ --header 'Content-Type: application/json' \ --data '{ "merchant_customer_reference": "<merchant-customer-reference>", "first_name": "Ananya", "last_name": "Sharma", "country_code": "91", "mobile_number": "<customer-mobile-number>", "email_id": "customer@example.com" }'

3. Create OT subscription

Create an OT direct-execution subscription. The amount configured here is stored as the subscription maximum limit and controls how much can be debited later through the Presentation API.

POST
<subscription-base-url>/api/v1/public/subscriptions/ot

Create a no-plan OT direct-execution subscription.

Bash
curl --request POST \ --url <subscription-base-url>/api/v1/public/subscriptions/ot \ --header 'Authorization: Bearer <access-token>' \ --header 'Merchant-ID: <merchant-id>' \ --header 'Content-Type: application/json' \ --data '{ "merchant_subscription_reference": "ot-direct-<unique-reference>", "customer_id": "<customer-id>", "plan_details": { "amount": 100, "currency": "INR", "validity_days": 30, "description": "Security deposit and estimated charges" }, "callback_url": "https://<your-domain>/subscription/callback", "merchant_metadata": { "source": "integration", "case_id": "ot-direct" } }'

Amount rules

  • plan_details.amount is an integer value in the smallest currency unit. For INR, public subscription integration uses paisa, so 100 means INR 1.
  • plan_details.amount is stored as the subscription maximum limit for Presentation API execution.
  • The OMS payment payment_amount.value used for mandate registration must match plan_details.amount.
  • Presentation amounts must be less than or equal to the stored subscription maximum limit.

4. Register mandate through Payments API

Use the order_id returned from subscription creation to create a UPI mandate registration payment.

POST
<payments-base-url>/api/pay/v1/orders/\{order_id\}/payments

Register the UPI OTM mandate through the Payments API.

Bash
curl --request POST \ --url <payments-base-url>/api/pay/v1/orders/<order-id>/payments \ --header 'Authorization: Bearer <access-token>' \ --header 'Content-Type: application/json' \ --header 'Accept: application/json' \ --header 'Request-ID: <uuid>' \ --header 'Request-Timestamp: <utc-timestamp>' \ --data '{ "payments": [ { "payment_method": "UPI", "merchant_payment_reference": "pay-<unique-reference>", "payment_amount": { "value": 100, "currency": "INR" }, "payment_option": { "upi_details": { "txn_mode": "INTENT" } }, "mandate_info": { "request_type": "CREATE_MANDATE" } } ] }'

Mandate registration rules

  • {order_id} must be the order_id returned by OT subscription creation.
  • payment_amount.value must match plan_details.amount from the subscription creation request.
  • mandate_info.request_type must be CREATE_MANDATE.
  • Use UPI intent mode for mandate authorization.
Simulator testing

In simulator-based non-production testing, no separate bank app or challenge_url handling may be required if the simulator marks mandate authorization as successful. For production, follow the authorization behaviour configured for your environment.

5. Wait for subscription activation

After successful mandate authorization or inquiry processing, the subscription moves from CREATED to ACTIVE.

Do not create a presentation until the subscription is ACTIVE or RESUMED.

6. Fetch OT subscription by ID

Use the OT subscription fetch API to verify the subscription state before presentation.

GET
<subscription-base-url>/api/v1/subscriptions/ot/\{subscription_id\}

Fetch an OT direct-execution subscription by ID.

Bash
curl --request GET \ --url <subscription-base-url>/api/v1/subscriptions/ot/<subscription-id> \ --header 'Authorization: Bearer <access-token>' \ --header 'Merchant-ID: <merchant-id>'

Expected response:

  • subscription_id matches the created subscription.
  • status is ACTIVE before debit execution.
  • execution_mode is DIRECT_EXECUTION.
  • plan_details.frequency is OT.

7. Filter subscriptions

You can filter OT subscriptions through the get-all subscriptions API.

GET
<subscription-base-url>/api/v1/public/subscriptions

List subscriptions and filter OT direct-execution records.

Bash
curl --request GET \ --url '<subscription-base-url>/api/v1/public/subscriptions?frequency=OT&status=ACTIVE&page=1&size=20' \ --header 'Authorization: Bearer <access-token>' \ --header 'Merchant-ID: <merchant-id>'

Useful filters:

FilterExample
Frequencyfrequency=OT
Statusstatus=CREATED or status=ACTIVE
Amountamount=100&amount_range=isEqual
Paginationpage=1&size=20
Sortingsort=subscriptionId,desc

8. Create presentation for active subscription

Create a presentation against the active OT-direct subscription to execute the debit.

POST
<subscription-base-url>/api/v1/public/presentations

Create a presentation for an active OT direct-execution subscription.

Bash
curl --request POST \ --url <subscription-base-url>/api/v1/public/presentations \ --header 'Authorization: Bearer <access-token>' \ --header 'Merchant-ID: <merchant-id>' \ --header 'Content-Type: application/json' \ --data '{ "subscription_id": "<subscription-id>", "amount": { "value": 100, "currency": "INR" }, "merchant_presentation_reference": "present-<unique-reference>", "merchant_retry_id": "retry-<unique-reference>" }'

Presentation rules

  • The subscription must be ACTIVE or RESUMED.
  • amount.value must be less than or equal to the stored subscription maximum limit derived from plan_details.amount.
  • merchant_presentation_reference must be unique for the merchant.
  • due_date is optional for OT direct execution. If supplied, it must not be in the past and must not be after the subscription end date.
  • Do not pass order_id unless you intentionally want to execute against an existing OMS order. If omitted, the service creates a fresh OMS order for direct execution.

9. Fetch presentation

Fetch the presentation by presentation_id or by your merchant_presentation_reference.

GET
<subscription-base-url>/api/v1/public/presentations/\{presentation_id\}

Fetch a presentation by presentation ID.

Bash
curl --request GET \ --url <subscription-base-url>/api/v1/public/presentations/<presentation-id> \ --header 'Authorization: Bearer <access-token>' \ --header 'Merchant-ID: <merchant-id>'
GET
<subscription-base-url>/api/v1/public/presentations/reference/\{merchant_presentation_reference\}

Fetch a presentation by merchant presentation reference.

Bash
curl --request GET \ --url <subscription-base-url>/api/v1/public/presentations/reference/<merchant-presentation-reference> \ --header 'Authorization: Bearer <access-token>' \ --header 'Merchant-ID: <merchant-id>'

Expected response:

  • subscription_id matches the OT-direct subscription.
  • merchant_presentation_reference matches the request.
  • order_id is present.
  • status reflects the current presentation execution state.

Webhooks

Configure webhook handling so your backend can update order state even if the customer closes the browser or app.

EventTriggered whenMerchant action
Subscription createdOT subscription is createdContinue to mandate registration
Subscription activatedCustomer mandate authorization succeedsMark the order as payment secured
Subscription chargedPresentation debit is executedFulfil or close the order
Presentation pendingPresentation request is createdTrack status and wait for final update
Presentation failedDebit execution failsCheck failure reason and retry only if safe
Subscription completed or expiredMandate lifecycle endsStop further debits against the subscription

Error handling

ScenarioExpected resultRecommended action
Duplicate subscription referenceDuplicate subscription validation errorGenerate a unique merchant_subscription_reference per subscription
Missing or invalid amountValidation errorPass a positive amount in the smallest currency unit
Invalid validityValidation errorKeep validity_days within your approved range
Presentation before activationInvalid subscription state errorWait until subscription status is ACTIVE or RESUMED
Presentation amount above ceilingRequested amount exceeds maximum limitRetry with an amount less than or equal to plan_details.amount
Duplicate presentation referenceDuplicate presentation validation errorGenerate a unique merchant_presentation_reference per presentation
Wrong merchant fetchNot found or unauthorized merchant-context responseFetch using the same Merchant-ID used during creation

Response statuses

StatusMeaningMerchant action
CREATEDOT subscription has been created and is awaiting mandate approvalContinue with mandate registration and customer authorization
ACTIVEMandate is approved and available for presentation executionUse Presentation API to debit funds
PENDINGPresentation request is created and processing is pendingFetch presentation status or wait for webhook notification
FAILEDRequest failedCheck error code and retry only if applicable
SUCCESSDebit or operation completed successfullyReconcile order and presentation state

Go-live checklist

  • OTM is enabled for your merchant account.
  • Your approved amount limits and validity window are documented.
  • Customer creation, OT subscription creation, mandate registration, subscription fetch, presentation create, and presentation fetch are tested end to end.
  • payment_amount.value in the Payments API matches plan_details.amount from subscription creation.
  • Presentation amount does not exceed the subscription maximum limit.
  • merchant_subscription_reference and merchant_presentation_reference are unique per merchant.
  • Webhook signatures are verified before updating order state.
  • Presentation is triggered only after fulfilment or final bill confirmation.
  • Customer-facing screens explain that funds are blocked first and debited through presentation execution.

Operational verification checklist

CheckExpected
Create OT subscription response201, generated subscription_id, generated order_id, status=CREATED, execution_mode=DIRECT_EXECUTION
Mandate registration payment2xx, data.status=PENDING, challenge_url may be present depending on environment
Authorization or inquiry processingSubscription moves to ACTIVE
Fetch by IDOT endpoint returns the created subscription
Get-all filtersfrequency=OT, status, and amount filters include the subscription
Presentation createResponse includes presentation_id, status=PENDING, and execution order_id
Presentation fetchResponse includes matching subscription_id, merchant_presentation_reference, and order_id
Webhook processingDownstream systems receive subscription and presentation events according to webhook configuration

Next steps

  • Review Use Cases to validate your product fit.
  • Review FAQs before production rollout.