BucksBox Developer DocsBucksBox Developer Docs
Getting Started
Merchant API
Partner API
SDK
GitHub
Getting Started
Merchant API
Partner API
SDK
GitHub
  • Overview
  • Authentication
  • UPI
  • QR
  • AEPS
  • DMT

DMT — Domestic Money Transfer

DMT lets a merchant's agent register a customer as a "remitter," add beneficiary bank accounts, and transfer money to them instantly over IMPS/NEFT via the NSDL network.

This page was rewritten against the live backend

The previous version used operation names (registerRemitter, queryRemitter, instantTransfer, …) and response fields (benefId, kycstatus, …) that don't match either paymentSystem's actual routes or the operation names in fintech-gateway's own operation reference. What follows is read directly from src/modules/interface/nsdl/dmt/routes/{dmt.customer,dmt.txn}.route.js and their controllers, plus field-level corrections made while building this repo's webpayment merchant portal against a live deployment — see that work's commit history for the concrete bugs this caught (the remitter-details response uses AviableLimit/AviabledayLimit/beneficiarydetail, not generically-named fields; the transaction/otp response's reference-id field name is genuinely unconfirmed — see below).

Two ways to call this

Same model as AEPS: a merchant calls paymentSystem directly with a Bearer JWT (unencrypted); a vendor calls fintech-gateway's POST /process/dmt/... — a 1:1 path-mirroring proxy, not an operation map — with an AES-256-GCM-encrypted body plus merchantId (see Partner API Security). Field names below apply to both paths identically.

/dmt/process and operation names are not live

An older gateway design (fintech-gateway/src/routes/dmt.js, an { "operation": "<name>", ...fields } envelope over POST /dmt/process, also referenced in fintech-gateway's qr-aeps-dmt-operations.md) exists in the codebase but is never registered in fintech-gateway/src/app.js — only the /process/* wildcard proxy is actually mounted. Operation names like customerOnboarding or remitterRegister describe that unreachable design; use the routes in the tables below instead.

Customer (remitter) onboarding

One-time KYC for a new customer, in order. Every endpoint is POST /dmt/customer/<route> direct (merchant JWT) or POST /process/dmt/customer/<route> via the gateway (vendor — add merchantId; see Vendor DMT).

PidData comes from the same RD-service Intent capture described on the AEPS page — one abstraction, reused for both services' biometric steps. See Supported Biometric (RD Service) Devices for which vendors are actually verified — Mantra (MFS100 fingerprint scanner) is the one this platform has been built and tested against; the DMT onboarding docs (dmt-api-docs) specifically reference a Mantra MSO1300E3 or similar RD-service-compliant device for the updatebiodetail step below. Response data fields below are NSDL's own onboarding-flow responses, passed through — where a field isn't called out explicitly, it isn't independently confirmed by this page; capture a real response in your environment to fill in the gaps.

GetDetail

POST /dmt/customer/GetDetail
Authorization: Bearer <token>
Content-Type: application/json

{ "mobile_number": "9876543210" }

This is also how you check whether a customer is already onboarded — statusCode: "00" with data means an existing, KYC'd remitter; a not-found response means run the onboarding chain below instead.

{ "statusCode": "00", "message": "success", "data": { "referenceid": "REF-...", "...": "remaining fields are NSDL's customer record, not independently confirmed here" } }

onboarding

POST /dmt/customer/onboarding
Content-Type: application/json

{
  "name":      "Ravi Kumar",
  "mobile_no": "9876543210",
  "email_id":  "ravi@example.com",
  "city":      "Mumbai",
  "state":     "Maharashtra"
}

email_id is optional; the rest are required. Creates the customer record and returns the referenceid every subsequent onboarding step needs.

{ "statusCode": "00", "message": "success", "data": { "referenceid": "REF-..." } }

request_otp

POST /dmt/customer/request_otp
Content-Type: application/json

{ "referenceid": "REF-..." }
{ "statusCode": "00", "message": "success", "data": { "otpreqid": "OTPREQ-..." } }

validate_otp

POST /dmt/customer/validate_otp
Content-Type: application/json

{ "otp": "123456", "otpreqid": "OTPREQ-...", "referenceid": "REF-..." }
{ "statusCode": "00", "message": "success" }

verify_UID

POST /dmt/customer/verify_UID
Content-Type: application/json

{ "aadhaarno": "999999999999", "referenceid": "REF-..." }
{ "statusCode": "00", "message": "success" }

updatebiodetail

POST /dmt/customer/updatebiodetail
Content-Type: application/json

{ "PidData": "<PidData XML from the RD service>", "aadhaarno": "999999999999", "referenceid": "REF-..." }
{ "statusCode": "00", "message": "success" }

submit

POST /dmt/customer/submit
Content-Type: application/json

{ "referenceid": "REF-..." }

Finalizes onboarding — NSDL confirmation. The customer is a usable DMT remitter after this succeeds.

{ "statusCode": "00", "message": "success", "data": { "...": "NSDL confirmation response, passed through" } }

statusCheck

GET /dmt/customer/statusCheck?referenceid=REF-...
Authorization: Bearer <token>

For a vendor call (no query string on an encrypted body), send referenceid in the encrypted body instead — confirm the controller reads req.query vs req.body for this specific route before relying on it in production, as GET+body is an edge case.

{ "statusCode": "00", "message": "success", "data": { "...": "NSDL onboarding-status response, passed through" } }

Customer Registration Flow

Remitter & beneficiaries

Every endpoint is POST /dmt/txn/v1/<route> direct (merchant JWT) or POST /process/dmt/txn/v1/<route> via the gateway (vendor — add merchantId). A first-time customer has no remitter yet — call remitter/details, and if it comes back not-found, call remitter/register then retry remitter/details.

Field names, confirmed against the controller

remitter/register takes sender_mobilenumber/senderId — notmobilenumber, despite what fintech-gateway's own operation-map comments and qr-aeps-dmt-operations.md say; that's a stale reference inside the gateway repo itself, not something this page got wrong. Likewise accountVerification reads receiver_account_no/receiverIfscCode (the same field names beneficiary/add uses) — an earlier version of the webpayment frontend sent accountnumber/ifsccode instead, which the backend silently never read, so every verification call failed before this was traced and fixed. All of the above is read directly from dmt.txn.controller.js, not inferred.

remitter/register

POST /dmt/txn/v1/remitter/register
Authorization: Bearer <token>
Content-Type: application/json

{ "sender_mobilenumber": "9876543210" }

Send sender_mobilenumber or senderId — name/address/DOB come from the customer record created during onboarding, not this call.

{ "statusCode": "00", "message": "success", "data": { "senderid": "686a3388-78c4-4897-8590-f3c5b2faade2", "registrationFeeCharged": 5 } }

remitter/details — confirmed response shape

POST /dmt/txn/v1/remitter/details
Content-Type: application/json

{ "sender_mobilenumber": "9876543210" }
{
  "statusCode": "00",
  "message": "sender found",
  "data": {
    "senderId": "686a3388-78c4-4897-8590-f3c5b2faade2",
    "status": true,
    "remitter": true,
    "TransactionDone": 0,
    "AviableLimit": 25000,
    "AviabledayLimit": 25000,
    "beneficiarydetail": [
      {
        "beneficiaryid": 16731,
        "beneficiaryname": "Balamurugan",
        "beneficiarymobilenumber": 0,
        "beneficiaryemailid": "",
        "bank": "DBS BANK LTD",
        "state": "KARNATAKA",
        "city": "BANGALORE URBAN",
        "branch": "INDIRANAGAR",
        "address": "GROUND FLOOR & FIRST FLOOR...",
        "ifscode": "DBSS0IN0753",
        "accountnumber": 8753210000020554,
        "beneficiarystatus": 1,
        "impsstatus": 1
      }
    ]
  }
}

Notes, all learned the hard way against a live deployment:

  • AviabledayLimit is the day's transfer ceiling; AviableLimit is what's still available to spend right now. There is no separate "used today" field — derive it as AviabledayLimit - AviableLimit.
  • The beneficiary list is embedded in this response (beneficiarydetail) — there is no separate "list beneficiaries" endpoint. beneficiaryid and accountnumber come back as numbers, not strings.
  • beneficiarystatus: 1 means the beneficiary is usable for a transfer.

beneficiary/accountVerification (penny-drop)

POST /dmt/txn/v1/beneficiary/accountVerification
Content-Type: application/json

{
  "receiver_account_no":  "8753210000020554",
  "receiverIfscCode":     "DBSS0IN0753",
  "senderId":             "686a3388-78c4-4897-8590-f3c5b2faade2",
  "receivername":         "Balamurugan",
  "receivermobilenumber": "9876500000",
  "receiveremailid":      "sa@gmail.com"
}

receiver_account_no/receiverIfscCode required; the rest optional (receiveremailid even defaults server-side if omitted).

{ "statusCode": "00", "message": "success", "data": { "status": 1 } }

data.status: 1 = verified, proceed with beneficiary/add; 0 = failed, notify the customer; -1/91 = ambiguous/unknown, requery after 30s.

beneficiary/add

POST /dmt/txn/v1/beneficiary/add
Content-Type: application/json

{
  "senderId":             "686a3388-78c4-4897-8590-f3c5b2faade2",
  "receivername":         "Balamurugan",
  "receiverIfscCode":     "DBSS0IN0753",
  "receiver_account_no":  "8753210000020554",
  "receivermobilenumber": "9876500000",
  "receiveremailid":      "sa@gmail.com",
  "address":              "Indiranagar",
  "city":                 "Bangalore",
  "state":                "Karnataka",
  "branch_contact":       ""
}

senderId, receivername, receiverIfscCode, receiver_account_no required; the rest optional.

{ "statusCode": "00", "message": "success", "data": { "beneficiaryid": 16731 } }

beneficiary/get

POST /dmt/txn/v1/beneficiary/get
Content-Type: application/json

{
  "senderId":            "686a3388-78c4-4897-8590-f3c5b2faade2",
  "receiverIfscCode":    "DBSS0IN0753",
  "receiver_account_no": "8753210000020554"
}
{ "statusCode": "00", "message": "success", "data": { "beneficiaryid": 16731, "beneficiarystatus": 1, "...": "same per-beneficiary shape as remitter/details#beneficiarydetail" } }

beneficiary/delete

POST /dmt/txn/v1/beneficiary/delete
Content-Type: application/json

{
  "senderId":            "686a3388-78c4-4897-8590-f3c5b2faade2",
  "receiver_account_no": "8753210000020554",
  "receiverIfscCode":    "DBSS0IN0753"
}
{ "statusCode": "00", "message": "Beneficiary deleted successfully" }

Remitter Registration Flow

Transactions

Every endpoint is POST /dmt/txn/v1/<route> direct (merchant JWT) or POST /process/dmt/txn/v1/<route> via the gateway (vendor — add merchantId). POST /dmt/txn/v1/transaction/balance is restricted to the aggregator role — the aggregator's own NSDL settlement balance, not a customer/remitter balance — and isn't reachable from a merchant or vendor credential, so it's omitted below.

transaction/otp

POST /dmt/txn/v1/transaction/otp
Authorization: Bearer <token>
Content-Type: application/json

{
  "senderId":            "686a3388-78c4-4897-8590-f3c5b2faade2",
  "amount":              500,
  "receiver_account_no": "8753210000020554",
  "receiverIfscCode":    "DBSS0IN0753"
}

Triggers NSDL to send an OTP to the customer's registered mobile — the OTP itself isn't returned in the response for you to relay yourself.

{ "statusCode": "00", "message": "OTP sent successfully" }

transaction/instant

POST /dmt/txn/v1/transaction/instant
Content-Type: application/json

{
  "senderId":             "686a3388-78c4-4897-8590-f3c5b2faade2",
  "BeneficiaryId":        16731,
  "amount":               500,
  "refId":                "REF-your-own-idempotency-key",
  "otp":                  "123456",
  "receiver_account_no":  "8753210000020554",
  "receiverIfscCode":     "DBSS0IN0753",
  "account_holder_name":  "Balamurugan",
  "receiverMobilenumber": "9876500000",
  "remarks":              "Rent"
}

otp comes from transaction/otp above; refId is your own reference. Combines OTP verification and the actual debit in one call — a wrong OTP and a genuine bank-side decline surface the same way (non-"00" statusCode), so don't assume a failure here means the OTP was wrong specifically.

{
  "statusCode": "00",
  "message": "Transaction successful",
  "data": {
    "rrn": "407812345678",
    "transactionId": "TXN-uuid",
    "amount": 500,
    "status": "SUCCESS"
  }
}

transaction/charges

POST /dmt/txn/v1/transaction/charges
Content-Type: application/json

{ "senderId": "686a3388-78c4-4897-8590-f3c5b2faade2", "amount": 500 }

Fee preview before actually sending transaction/instant.

{ "statusCode": "00", "message": "success", "data": { "charge": 5, "totalAmount": 505 } }

transaction/requery

POST /dmt/txn/v1/transaction/requery
Content-Type: application/json

{ "refId": "REF-your-own-idempotency-key" }

Use when transaction/instant came back pending/ambiguous.

{ "statusCode": "00", "message": "success", "data": { "status": "SUCCESS", "rrn": "407812345678" } }

transaction/refundOTP

POST /dmt/txn/v1/transaction/refundOTP
Content-Type: application/json

{ "refereanceId": "TXN-uuid" }

⚠️ Field name is genuinely spelled refereanceId (typo) in dmt.txn.controller.js#refundOTP — not a mistake on this page, that's what the backend reads.

{ "statusCode": "00", "message": "OTP sent for refund" }

refund

POST /dmt/txn/v1/refund
Content-Type: application/json

{ "transactionId": "TXN-uuid", "refId": "REF-...", "otp": "123456" }

otp comes from transaction/refundOTP above.

{ "statusCode": "00", "message": "Refund processed successfully", "data": { "refundId": "RFD-uuid", "amount": 500 } }

Transaction Flow

A wrong OTP and a genuine bank-side decline both surface as a non-"00"statusCode from transaction/instant — don't assume a failure here means the OTP specifically was wrong.

Access control

  • Both customer-onboarding and transaction routes accept either role (checkRole("merchant", "vendor")) — a merchant JWT or a vendor gateway-authenticated call both work.
  • A vendor acting on behalf of a merchant must include merchantId in the body, same pattern as AEPS — paymentSystem resolves req.user?.merchantId ?? req.body.merchantId. See Vendor DMT.
  • POST /dmt/txn/v1/login exists (merchant/vendor) but isn't part of the customer/transaction flow above — it's a separate NSDL session bootstrap, analogous to AEPS's automatic reqAuth.
  • BC (business correspondent) onboarding — bcRegister, bcAgentRegister, bcAgentInitialize, bcStatus — is restricted to aggregator/vendor roles by design; a direct merchant integration can't call these.

First-Transfer Flow — All Three Phases Together

See Customer Registration Flow, Remitter Registration Flow, and Transaction Flow above for each phase's full decision logic and exact field names.

Prev
AEPS