---
title: Integration Steps
slug: one-time-mandate/integration-steps
excerpt: >-
  Integrate UPI One-Time Mandate with OT direct-execution subscriptions, mandate
  registration, and presentation execution.
hidden: false
sidebar_order: 2
metadata:
  title: UPI One-Time Mandate Integration Steps | Pine Labs Online Payments
  description: >-
    Step-by-step guide to integrate UPI One-Time Mandate with OT
    direct-execution subscriptions, Payments mandate registration, and
    Presentation API execution.
  keywords: >-
    UPI OTM integration, UPI One-Time Mandate API, OT direct execution,
    presentation API, create mandate
---
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.

<Callout type="info" title="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.
</Callout>

## 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

<Steps>
  <Step title="Ensure the customer exists">
    Create or reuse a Pine Labs Online customer profile and keep the `customer_id` available for subscription creation.
  </Step>
  <Step title="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`.
  </Step>
  <Step title="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`.
  </Step>
  <Step title="Wait for activation">
    Wait for the mandate callback or inquiry processing to move the subscription to `ACTIVE`.
  </Step>
  <Step title="Create presentation">
    Create a Presentation API request against the active subscription to execute the debit.
  </Step>
</Steps>

## 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>
```

<Callout type="warning" title="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.
</Callout>

## 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"
  }'
```

```json
{
  "access_token": "<access-token>",
  "expires_in": 3600
}
```

<Callout type="warning" title="Keep credentials server-side">
Do not store API credentials, access tokens, or client secrets in frontend code, mobile apps, logs, screenshots, or public repositories.
</Callout>

## 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.

<EndpointCard method="POST" path="<subscription-base-url>/api/v1/public/subscriptions/ot" description="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"
    }
  }'
```

```json
{
  "subscription_id": "v1-sub-...",
  "order_id": "<order-id>",
  "status": "CREATED",
  "execution_mode": "DIRECT_EXECUTION",
  "redirect_url": "<redirect-url>"
}
```

### 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.

<EndpointCard method="POST" path="<payments-base-url>/api/pay/v1/orders/{order_id}/payments" description="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"
        }
      }
    ]
  }'
```

```json
{
  "data": {
    "order_id": "<order-id>",
    "status": "PENDING",
    "challenge_url": "upi://mandate?...",
    "payments": [
      {
        "status": "PENDING"
      }
    ]
  }
}
```

### 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.

<Callout type="info" title="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.
</Callout>

## 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.

<EndpointCard method="GET" path="<subscription-base-url>/api/v1/subscriptions/ot/{subscription_id}" description="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.

<EndpointCard method="GET" path="<subscription-base-url>/api/v1/public/subscriptions" description="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>'
```

```json
{
  "page": {
    "size": 20,
    "total_elements": 1,
    "total_pages": 1,
    "number": 1
  },
  "subscriptions": [
    {
      "subscription_id": "v1-sub-...",
      "execution_mode": "DIRECT_EXECUTION"
    }
  ]
}
```

Useful filters:

| Filter | Example |
|---|---|
| Frequency | `frequency=OT` |
| Status | `status=CREATED` or `status=ACTIVE` |
| Amount | `amount=100&amount_range=isEqual` |
| Pagination | `page=1&size=20` |
| Sorting | `sort=subscriptionId,desc` |

## 8. Create presentation for active subscription

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

<EndpointCard method="POST" path="<subscription-base-url>/api/v1/public/presentations" description="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>"
  }'
```

```json
{
  "subscription_id": "v1-sub-...",
  "presentation_id": "v1-cyc-...",
  "amount": {
    "value": 100,
    "currency": "INR"
  },
  "merchant_presentation_reference": "present-...",
  "status": "PENDING",
  "order_id": "v1-..."
}
```

### 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`.

<EndpointCard method="GET" path="<subscription-base-url>/api/v1/public/presentations/{presentation_id}" description="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>'
```

<EndpointCard method="GET" path="<subscription-base-url>/api/v1/public/presentations/reference/{merchant_presentation_reference}" description="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.

| Event | Triggered when | Merchant action |
|---|---|---|
| Subscription created | OT subscription is created | Continue to mandate registration |
| Subscription activated | Customer mandate authorization succeeds | Mark the order as payment secured |
| Subscription charged | Presentation debit is executed | Fulfil or close the order |
| Presentation pending | Presentation request is created | Track status and wait for final update |
| Presentation failed | Debit execution fails | Check failure reason and retry only if safe |
| Subscription completed or expired | Mandate lifecycle ends | Stop further debits against the subscription |

## Error handling

| Scenario | Expected result | Recommended action |
|---|---|---|
| Duplicate subscription reference | Duplicate subscription validation error | Generate a unique `merchant_subscription_reference` per subscription |
| Missing or invalid amount | Validation error | Pass a positive amount in the smallest currency unit |
| Invalid validity | Validation error | Keep `validity_days` within your approved range |
| Presentation before activation | Invalid subscription state error | Wait until subscription status is `ACTIVE` or `RESUMED` |
| Presentation amount above ceiling | Requested amount exceeds maximum limit | Retry with an amount less than or equal to `plan_details.amount` |
| Duplicate presentation reference | Duplicate presentation validation error | Generate a unique `merchant_presentation_reference` per presentation |
| Wrong merchant fetch | Not found or unauthorized merchant-context response | Fetch using the same `Merchant-ID` used during creation |

## Response statuses

| Status | Meaning | Merchant action |
|---|---|---|
| `CREATED` | OT subscription has been created and is awaiting mandate approval | Continue with mandate registration and customer authorization |
| `ACTIVE` | Mandate is approved and available for presentation execution | Use Presentation API to debit funds |
| `PENDING` | Presentation request is created and processing is pending | Fetch presentation status or wait for webhook notification |
| `FAILED` | Request failed | Check error code and retry only if applicable |
| `SUCCESS` | Debit or operation completed successfully | Reconcile 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

| Check | Expected |
|---|---|
| Create OT subscription response | `201`, generated `subscription_id`, generated `order_id`, `status=CREATED`, `execution_mode=DIRECT_EXECUTION` |
| Mandate registration payment | `2xx`, `data.status=PENDING`, `challenge_url` may be present depending on environment |
| Authorization or inquiry processing | Subscription moves to `ACTIVE` |
| Fetch by ID | OT endpoint returns the created subscription |
| Get-all filters | `frequency=OT`, `status`, and `amount` filters include the subscription |
| Presentation create | Response includes `presentation_id`, `status=PENDING`, and execution `order_id` |
| Presentation fetch | Response includes matching `subscription_id`, `merchant_presentation_reference`, and `order_id` |
| Webhook processing | Downstream systems receive subscription and presentation events according to webhook configuration |

## Next steps

- Review [Use Cases](/upi-one-time-mandate/use-cases) to validate your product fit.
- Review [FAQs](/upi-one-time-mandate/faqs) before production rollout.
