Commerce Platforms 13 min read Aug 17, 2026

Paytm Dynamic QR Payment Integration in a Custom POS: Auto-Detecting Payments

In A Custom POS Auto-Detecting Payments

Written by Mukesh · Reviewed Aug 17, 2026

Post
If you are building your own POS for a restaurant, retail counter, hotel, canteen, kiosk, or internal ERP, there is a point where a printed UPI QR code stops being enough.

The customer can scan a static QR and pay, but your POS still has no reliable way to connect that payment to the order. Someone at the counter has to check the payment manually, and that becomes a problem when several orders are being paid at the same time.

With a Dynamic QR flow, the POS creates a QR for the specific order. The amount and order reference are tied to that payment. Once the payment reaches a terminal state, Paytm can send a server-to-server callback to your backend, and your POS can update the order without waiting for the cashier to check the payment manually.

This guide explains that flow and the main pieces you need to handle when integrating Paytm Dynamic QR into a custom POS.


1. Static QR vs Dynamic QR

The difference is mainly about how the payment is linked back to your order.


                      Static QR               Dynamic QR

Amount Customer enters the POS creates the payment amount with the order amount

Order tracking The same QR can be used Each order gets its own for different orders QR and order ID

Payment detection Usually needs manual Can use a webhook or verification status API

Typical use Small counters and Restaurant billing, simple payments hotel PMS, retail POS, kiosks

With a Dynamic QR, the POS creates a new QR when the bill is ready. The QR contains the payment information for that order, including the amount and transaction reference.

The customer scans it with a UPI app and completes the payment. Your backend then uses Paytm's payment response to match the transaction with the order.


2. How the POS Payment Flow Works

The complete flow looks like this:

  1. The cashier creates an order in the POS. The order gets a unique Order ID and an amount.
  2. The POS backend calls Paytm's Create QR Code API.
  3. Paytm returns QR information, including qrData and, depending on the response, a base64 QR image.
  4. The POS displays the QR on the customer-facing screen or prints it if that is part of the setup.
  5. The customer scans the QR and completes the payment using a UPI app.
  6. Paytm sends a server-to-server callback to the configured webhook when the transaction reaches a terminal state.
  7. Your backend verifies the callback and updates the order.
  8. The POS screen can then show the payment as successful and trigger the receipt flow.

It is also worth keeping the Transaction Status API as a fallback. If the webhook is delayed or does not reach your server, the POS can check the transaction status instead of leaving the cashier waiting indefinitely.

The basic sequence is therefore:

Create order → Generate QR → Customer pays → Receive confirmation → Update order


3. What You Need Before Starting

3.1 Business / Merchant Account

You need an active Paytm for Business merchant account.

The availability and onboarding process should be confirmed with Paytm before starting the production integration.

3.2 Credentials

The integration requires merchant credentials such as:

  • MID (Merchant ID) --- identifies the merchant.
  • Merchant Key --- a secret used by the server for authentication/checksum operations.
  • POS ID --- identifies the physical POS/counter. The format used in this guide is StoreID_POSID.

Keep the Merchant Key on the server. It should never be included in frontend JavaScript, a mobile client, or any code that can be inspected by the customer.

3.3 Customer-Facing Display

For a POS setup, the QR needs somewhere to be shown to the customer.

A customer-facing display (CFD) can be used for this. If your setup does not have a CFD, the QR can also be printed as part of the invoice where supported by your payment flow.

3.4 Server

Your backend needs a publicly accessible HTTPS endpoint for Paytm's callback.

The guide assumes:

  • A public HTTPS webhook endpoint
  • TLS 1.2 support
  • A backend that can receive and process POST requests
  • A database where the POS can store the order and payment information

4. Integration

Step 1 --- Get the Merchant Credentials

Get the MID and Merchant Key through the Paytm business onboarding process.

Store them as server-side environment variables. Do not commit them to Git and do not send the Merchant Key to the browser.

For example:

PAYTM_MID=INTEGR7769XXXXXX93833
PAYTM_MERCHANT_KEY=your_secret_key_here
PAYTM_POS_ID=S12_123

The values above are examples. Use the credentials provided for your own merchant account.


Step 2 --- Create the QR Code

When the POS has a bill ready for payment, the backend calls Paytm's Create QR Code API.

The important part here is that the backend makes the Paytm API request. The POS frontend should not contain the Merchant Key.

Staging endpoint

POST https://securestage.paytmpayments.com/paymentservices/qr/create

Example request

{
  "body": {
    "mid": "INTEGR7769XXXXXX93833",
    "orderId": "ORD98765",
    "amount": "1303.00",
    "businessType": "UPI_QR_CODE",
    "posId": "S12_123",
    "orderDetails": "Table 4 - Dinner Order",
    "expiryDate": "2026-08-17 22:30:00"
  },
  "head": {
    "clientId": "C11",
    "version": "v1",
    "signature": "{generated_checksum}"
  }
}

The exact request fields and values should be checked against the Paytm documentation and the configuration provided for your merchant account before moving to production.

Fields used in the request

Field Purpose


mid Merchant ID orderId Unique order reference used for reconciliation amount Payment amount, with up to two decimal places businessType UPI_QR_CODE posId POS identifier expiryDate Optional expiry value for the QR signature Checksum/signature generated for the request

The orderId is particularly important. Your POS needs to be able to map the eventual payment back to exactly one order.

For a multi-counter POS, do not assume that Paytm will solve order ID collisions for you. Enforce uniqueness in your own database as well.

Example response

A successful response can contain information like:

{
  "resultInfo": {
    "resultStatus": "SUCCESS",
    "resultCode": "QR_0001"
  },
  "qrCodeId": "200005050XXXXXUHF4HA7J5X",
  "qrData": "upi://pay?pa=merchant@paytm&am=1303.00&tr=ORD98765...",
  "image": "base64_encoded_qr_image_string"
}

There are two useful values here:

  • qrData is the raw UPI string. You can use it with a QR library if your application needs to render the QR itself.
  • image is a base64-encoded image returned by Paytm, which can be displayed directly in the POS UI after converting it into the appropriate image source.

Step 3 --- Display the QR

Once the backend receives the QR response, send the required data to the customer-facing POS screen.

A few practical points from the integration requirements:

  • Keep the QR on a white background.
  • Use at least a 1.5" × 1.5" display area for scanning.
  • Leave roughly 0.5" of white space around the QR.
  • If you generate the image yourself, use the qrData returned by Paytm as the source data.

The QR needs to be large and clear enough for a customer to scan it from the position where the display is installed.


Step 4 --- Customer Pays

The customer scans the QR with a UPI application and completes the payment.

Paytm does not have to be the app used for scanning if the QR is a standard UPI QR. Depending on the payment flow, customers can use apps such as Paytm, Google Pay, or PhonePe.

The important part for your POS is what happens after the customer completes the payment. Your backend needs a reliable confirmation before changing the order to PAID.


5. Detecting the Payment

This is the part that makes the Dynamic QR useful in a POS.

There are two mechanisms to consider:

  1. Webhook / S2S callback --- primary, because it can notify your server when the payment reaches a terminal state.
  2. Transaction Status API --- fallback, so the POS can check the payment when a callback is delayed or unavailable.

A. Webhook / S2S Callback

Paytm sends a POST request to the webhook URL configured for your merchant when the transaction reaches a terminal state such as success or failure.

The callback endpoint belongs to your backend. It should not be handled directly by the POS browser.

A simplified PHP handler looks like this:

<?php

// webhook.php

$rawPost = file_get_contents('php://input');
$data = json_decode($rawPost, true);

// 1. Verify the Paytm checksum before processing anything.
if (!verifyPaytmChecksum($data)) {
    http_response_code(400);
    exit('Invalid checksum');
}

// 2. Read the payment information.
$orderId = $data['ORDERID'];
$status  = $data['STATUS'];
$txnId   = $data['TXNID'];
$amount  = $data['TXNAMOUNT'];

// 3. Find the order in your database.
$order = getOrderById($orderId);

if (!$order) {
    http_response_code(200);
    exit('Order not found');
}

// 4. Ignore a callback that has already been processed.
if ($order['status'] === 'PAID') {
    http_response_code(200);
    exit('Already processed');
}

// 5. Make sure the payment amount matches the order.
if ($amount != $order['expected_amount']) {
    logSuspiciousActivity(
        $orderId,
        $amount,
        $order['expected_amount']
    );

    http_response_code(200);
    exit();
}

// 6. Mark the order as paid only for a successful transaction.
if ($status === 'TXN_SUCCESS') {
    markOrderAsPaid($orderId, $txnId);

    // Notify the POS screen using your chosen mechanism.
    notifyPOSScreen($orderId);
}

http_response_code(200);
echo 'OK';

This example is intentionally focused on the payment flow. The actual checksum verification, database operations, logging, and POS notification need to be implemented for your application.

Why the checks matter

The callback should not be treated as "payment successful" simply because a request arrived at your endpoint.

Your backend should:

  1. Verify the checksum/signature.
  2. Find the order using the order reference.
  3. Check whether the payment has already been processed.
  4. Compare the received amount with the amount stored against the order.
  5. Check the transaction status.
  6. Save the transaction ID.
  7. Notify the POS after the database update succeeds.

That sequence gives you a much safer payment state transition.

Return 200 OK

The webhook handler should return 200 OK after the callback has been received and handled, including cases where the callback is a duplicate.

If your endpoint returns an error response, Paytm may retry the callback. Your application therefore needs idempotency handling so that a retry does not create a second payment action.


6. Transaction Status API as a Fallback

A webhook is the main notification mechanism, but a POS should still have a way to recover when the callback is delayed or missed.

For that case, use the Transaction Status API.

The polling schedule used in this guide is:

Query Time since QR was sent


1st 15 seconds 2nd 25 seconds 3rd 30 seconds 4th 35 seconds 5th 40 seconds 6th 45 seconds 7th 50 seconds 8th 55 seconds

If the transaction is still unresolved after the polling attempts, the POS can show a Check Status action so the cashier can request another status check.

The exact polling behavior should follow the current Paytm documentation for your integration rather than being hard-coded from an old implementation.


7. Closing the Bill After Payment

Once your backend has confirmed a successful payment:

  1. Update the order status in the database.
  2. Store the Paytm Transaction ID against the order.
  3. Notify the POS or customer-facing screen.
  4. Update the UI to show that payment is complete.
  5. Trigger the receipt/printing flow if the POS is configured to do so.

The database update should happen before you tell the POS that the payment is complete.

For example:

Paytm confirmation
       ↓
Verify callback
       ↓
Find order
       ↓
Verify amount
       ↓
Update order = PAID
       ↓
Save transaction ID
       ↓
Notify POS
       ↓
Print receipt

This also gives you a clean record for later reconciliation.


8. Refunds

Refunds need a separate flow from the original payment.

If a paid order is cancelled or returned:

  • Send the refund request with a unique REFID.
  • Use the Refund Status API to check the result.
  • If the refund fails, a retry can be considered according to the returned state.
  • If the refund is pending, do not immediately send another refund request. Wait for the status to resolve first.

The main reason for being careful here is simple: sending another refund request while the first one is still pending can result in an unintended duplicate refund.


9. Security Checks

Payment callbacks should be treated as untrusted input until they have been verified.

1. Keep the Merchant Key on the server

Never expose the Merchant Key in:

  • JavaScript
  • Browser code
  • Android/iOS client code
  • Public Git repositories
  • Customer-facing POS configuration

The Paytm API request should be made by your backend.

2. Verify the checksum

Do not mark an order as paid just because someone can POST a payload containing:

STATUS=TXN_SUCCESS

Verify the Paytm checksum/signature first.

3. Verify the amount

Compare the amount received in the payment response with the amount stored against the order.

If the order is for 1303.00 and the callback contains a different amount, do not mark that order as paid.

4. Handle duplicate callbacks

Callbacks can be retried. Your database logic should therefore be idempotent.

A simple approach is to check the current order state before processing:

If order is already PAID
    → do nothing
    → return 200

Otherwise
    → validate callback
    → validate amount
    → process payment

For larger systems, you can also keep a separate payment transaction table and enforce uniqueness on the external transaction/reference identifiers.

5. Use HTTPS

The webhook endpoint should use HTTPS and be publicly reachable by Paytm.

6. Keep Order IDs unique

Your Order ID must uniquely identify the payment within your merchant setup.

This becomes especially important when multiple POS terminals are operating at the same time.


10. Common Problems in a POS Integration


Problem What can happen


Reusing an Order ID Paytm can reject the request and reconciliation becomes unreliable

Depending only on the webhook A delayed or failed callback can leave the cashier waiting

Invalid amount format The API can reject the request

Calling Paytm directly from the Merchant credentials can be exposed frontend

Not checking the checksum A forged callback could be treated as a payment

Reusing the same QR Different orders can no longer be reliably separated

Duplicate POS IDs Multi-counter setups can become difficult to reconcile

Very short API timeouts Requests can fail when your system has additional network or proxy hops

Most of these problems are not difficult to prevent. The important thing is to decide how your POS will handle them before going live.


11. Where This Pattern Fits

The same payment flow can be used in several POS scenarios.

Restaurant pay-at-table

A waiter creates the order and generates a QR against that order. The customer pays from the table. Once the backend receives confirmation, the billing or table screen can update.

Retail counter

The cashier creates the bill and the customer-facing display shows the QR. After payment confirmation, the POS moves to the next step and can print the receipt.

Hotel PMS

A room bill can be associated with a Dynamic QR at checkout. The payment confirmation can then be stored against the guest's billing record.

Self-ordering kiosk

The kiosk creates the order, displays the QR, and waits for server-side confirmation before continuing with the order workflow.

Delivery payment

A delivery application can show a payment QR associated with the delivery order. Once the payment is confirmed, the application can update the delivery/payment state.

Web or Smart TV checkout

A web-based checkout can display the QR while the backend waits for payment confirmation. The page can update after the server receives the result.


12. Go-Live Checklist

Before moving the integration to production, test the complete payment lifecycle rather than only testing QR generation.

  • [ ] Create a QR in staging.
  • [ ] Complete a real test payment.
  • [ ] Receive and verify the webhook.
  • [ ] Test the Transaction Status API.
  • [ ] Confirm that duplicate callbacks do not duplicate processing.
  • [ ] Enforce Order ID uniqueness at the database level.
  • [ ] Validate the payment amount on the server.
  • [ ] Verify valid and invalid checksums.
  • [ ] Confirm the webhook is publicly accessible over HTTPS.
  • [ ] Test the POS screen update after payment.
  • [ ] Test the manual Check Status action.
  • [ ] Test timeout and retry behavior.
  • [ ] Decide how stuck or unknown transactions will be handled.
  • [ ] Keep production credentials separate from staging credentials.
  • [ ] Store the Paytm Transaction ID with the order.
  • [ ] Test refund success.
  • [ ] Test refund failure and the retry path.
  • [ ] Test refund pending and make sure it is not submitted again immediately.
  • [ ] Check application logs for API errors and unexpected callbacks.

13. Paytm Documentation

Use the official Paytm documentation for the exact API contract and the current merchant-specific requirements:

The API details, onboarding requirements, supported fields, and callback behavior can change, so check the current Paytm documentation before implementing the production version.


14. The Main Idea

For a custom POS, the useful part of Dynamic QR is not simply generating a QR image.

The POS needs to maintain the connection between three things:

POS Order
    ↓
Dynamic QR / Payment Request
    ↓
Paytm Transaction

When the customer pays, your backend needs to bring that transaction back to the same order:

Paytm callback
      ↓
Verify checksum
      ↓
Find Order ID
      ↓
Check amount
      ↓
Check current payment state
      ↓
Mark order as PAID
      ↓
Save Transaction ID
      ↓
Notify POS

That is what allows the cashier or kiosk to know that the payment is actually complete without manually checking a Paytm app.

If you are building this in PHP, Laravel, Symfony, or another backend stack, keep the Paytm integration on the server side and let the POS frontend deal with the order state and screen updates. The payment credentials and verification logic should stay on the backend.

One more thing to confirm before starting development is merchant access. The Dynamic QR flow described here depends on the Paytm merchant setup and API access available to your account, so confirm that first rather than building against an integration that your merchant account cannot use.

About the author

Mukesh is the developer behind InfoMukesh, writing practical notes from hands-on work with PHP, Laravel, e-commerce platforms, AI, and web applications.

Related reading