Create a UPI pay-in
/v2/upi/payinEncrypt these fields as JSON and send the result as encrypted_data. The pay-in guide has working code in five languages.
| Field | Required | Rule |
|---|---|---|
first_name, last_name |
Yes | 2 to 100 characters |
email |
Yes | valid email |
phone_number |
Yes | 10 digits, first digit 6 to 9, no country code |
amount |
Yes | decimal INR, for example "100.00" |
membership_duration |
Yes | months as a member of your platform; below 3 is refused |
response_url |
Yes | full URL with a public domain |
webhook_url |
Yes | full URL with a public domain |
merchant_order_id |
No | your unique reference; strongly recommended |
customer_user_id |
No | your customer reference |
upi |
No | the customer's UPI ID |
pg_id |
No | terminal ID, only if we issued you several |
address, city, state, zip |
No | 2 to 250 characters |
country |
No | two capital letters |
Every pay-in is in INR; currency is ignored.
Reading the answer. Most requests return status: "authenticate" with an authenticate_url and usually a upi_intent. That means the pay-in exists and is waiting for the customer. It is not paid yet. The final result arrives by webhook, and you can ask for it with the status call.
Errors come back with HTTP 200 and status: "validation_error", "blocked" or "failed". Only authentication problems use HTTP 401. Always read status in the body.
Headers
| Header | Required | Value |
|---|---|---|
Authorization | Yes | Bearer <API_KEY>: your API secret key. Your API secret key, sent as Authorization: Bearer <API_KEY>. It is shown under Settings in the merchant console, where you can also reset it. Keep it on your server. |
Content-Type | Yes | application/json |
Request fields, before encryption
Serialise these as JSON, encrypt them as described in the authentication guide, and send only the ciphertext.
{
"merchant_order_id": "ORDER-10001",
"first_name": "Asha",
"last_name": "Verma",
"email": "customer@example.com",
"phone_number": "9000000001",
"amount": "100.00",
"membership_duration": 3,
"response_url": "https://merchant.example.com/payments/return",
"webhook_url": "https://merchant.example.com/webhooks/payin"
}Request body sent
{
"encrypted_data": "<BASE64_CIPHERTEXT>"
}Responses
The pay-in was created, refused or failed. Read status in the body.
{
"status": "authenticate",
"authenticate_url": "https://api.payeeglobal.com/<hosted-page>/<reference>",
"upi_intent": "upi://pay?pa=<payee_vpa>&pn=<payee_name>&am=100.00&cu=INR&tr=<reference>",
"merchant_order_id": "ORDER-10001",
"transaction_id": "T4K2Q9ZB1790000000000",
"amount": "100.00",
"message": "QR Generated."
}{
"status": "validation_error",
"message": "Duplicate order_id, field must be unique."
}{
"status": "validation_error",
"message": "Phone number must be 10 digits"
}{
"status": "validation_error",
"message": "Invalid data encryption."
}{
"status": "blocked",
"merchant_order_id": "ORDER-10002",
"transaction_id": "T8M3P0QC1790000000000",
"amount": "100.00",
"message": "Daily transaction amount limit exceeded."
}{
"status": "failed",
"merchant_order_id": "ORDER-10003",
"transaction_id": "T1C7W4XD1790000000000",
"amount": "100.00",
"message": "Payin declined, contact support for assistance."
}{
"status": "unauthorized",
"message": "Invalid secret key."
}Example in five languages
API_KEY='<API_KEY>'
SECRET_HASH='<SECRET_HASH>' # base64, from Settings in the merchant console
IV_HEX='<IV_HEX>' # 32 hex characters, from the API documentation page in the console
# The AES-256 key as hex, decoded from the base64 secret hash
KEY_HEX=$(printf '%s' "$SECRET_HASH" | openssl base64 -d -A | od -An -v -tx1 | tr -d ' \n')
FIELDS='{"merchant_order_id":"<merchant_order_id>","first_name":"<first_name>","last_name":"<last_name>","email":"<email>","phone_number":"<phone_number>","amount":"<amount>","membership_duration":"<membership_duration>","response_url":"<response_url>","webhook_url":"<webhook_url>"}'
ENCRYPTED=$(printf '%s' "$FIELDS" | openssl enc -aes-256-cbc -K "$KEY_HEX" -iv "$IV_HEX" -base64 -A)
curl -sS -X POST 'https://api.payeeglobal.com/v2/upi/payin' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d "{\"encrypted_data\":\"$ENCRYPTED\"}"// Node 18 or later. No dependencies.
import crypto from 'node:crypto';
const BASE_URL = 'https://api.payeeglobal.com';
const API_KEY = '<API_KEY>';
const SECRET_HASH = '<SECRET_HASH>'; // base64, from Settings in the merchant console
const IV_HEX = '<IV_HEX>'; // 32 hex characters, from the API documentation page in the console
function encrypt(fields) {
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(SECRET_HASH, 'base64'), Buffer.from(IV_HEX, 'hex'));
return Buffer.concat([cipher.update(JSON.stringify(fields), 'utf8'), cipher.final()]).toString('base64');
}
const fields = {
merchant_order_id: '<merchant_order_id>',
first_name: '<first_name>',
last_name: '<last_name>',
email: '<email>',
phone_number: '<phone_number>',
amount: '<amount>',
membership_duration: '<membership_duration>',
response_url: '<response_url>',
webhook_url: '<webhook_url>',
};
const res = await fetch(`${BASE_URL}/v2/upi/payin`, {
method: 'POST',
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ encrypted_data: encrypt(fields) }),
});
const data = await res.json();
if (data.status === 'authenticate') {
// Created and waiting for the customer: redirect to authenticate_url, or show upi_intent as a QR code.
console.log(data.transaction_id, data.authenticate_url);
} else {
console.error(res.status, data.status, data.message);
}# Python 3.9 or later: pip install requests cryptography
import base64
import json
import requests
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
BASE_URL = "https://api.payeeglobal.com"
API_KEY = "<API_KEY>"
SECRET_HASH = "<SECRET_HASH>" # base64, from Settings in the merchant console
IV_HEX = "<IV_HEX>" # 32 hex characters, from the API documentation page in the console
def encrypt(fields: dict) -> str:
padder = padding.PKCS7(128).padder()
plain = padder.update(json.dumps(fields).encode("utf-8")) + padder.finalize()
encryptor = Cipher(algorithms.AES(base64.b64decode(SECRET_HASH)), modes.CBC(bytes.fromhex(IV_HEX))).encryptor()
return base64.b64encode(encryptor.update(plain) + encryptor.finalize()).decode("ascii")
fields = {
"merchant_order_id": "<merchant_order_id>",
"first_name": "<first_name>",
"last_name": "<last_name>",
"email": "<email>",
"phone_number": "<phone_number>",
"amount": "<amount>",
"membership_duration": "<membership_duration>",
"response_url": "<response_url>",
"webhook_url": "<webhook_url>",
}
res = requests.post(
f"{BASE_URL}/v2/upi/payin",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"encrypted_data": encrypt(fields)},
timeout=30,
)
data = res.json()
if data.get("status") == "authenticate":
# Created and waiting for the customer: redirect to authenticate_url, or show upi_intent as a QR code.
print(data["transaction_id"], data["authenticate_url"])
else:
print(res.status_code, data.get("status"), data.get("message"))// Go 1.20 or later. Standard library only.
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
)
const (
baseURL = "https://api.payeeglobal.com"
apiKey = "<API_KEY>"
secretHash = "<SECRET_HASH>" // base64, from Settings in the merchant console
ivHex = "<IV_HEX>" // 32 hex characters, from the API documentation page in the console
)
func encrypt(fields any) (string, error) {
key, err := base64.StdEncoding.DecodeString(secretHash)
if err != nil {
return "", err
}
iv, err := hex.DecodeString(ivHex)
if err != nil {
return "", err
}
plain, err := json.Marshal(fields)
if err != nil {
return "", err
}
pad := aes.BlockSize - len(plain)%aes.BlockSize
plain = append(plain, bytes.Repeat([]byte{byte(pad)}, pad)...)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
out := make([]byte, len(plain))
cipher.NewCBCEncrypter(block, iv).CryptBlocks(out, plain)
return base64.StdEncoding.EncodeToString(out), nil
}
func main() {
fields := map[string]string{
"merchant_order_id": "<merchant_order_id>",
"first_name": "<first_name>",
"last_name": "<last_name>",
"email": "<email>",
"phone_number": "<phone_number>",
"amount": "<amount>",
"membership_duration": "<membership_duration>",
"response_url": "<response_url>",
"webhook_url": "<webhook_url>",
}
encrypted, err := encrypt(fields)
if err != nil {
panic(err)
}
body, _ := json.Marshal(map[string]string{"encrypted_data": encrypted})
req, _ := http.NewRequest("POST", baseURL+"/v2/upi/payin", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
panic(err)
}
if data["status"] == "authenticate" {
// Created and waiting for the customer: redirect to authenticate_url, or show upi_intent as a QR code.
fmt.Println(data["transaction_id"], data["authenticate_url"])
} else {
fmt.Println(res.StatusCode, data["status"], data["message"])
}
}<?php
// PHP 7.4 or later with the openssl and curl extensions.
$baseUrl = 'https://api.payeeglobal.com';
$apiKey = '<API_KEY>';
$secretHash = '<SECRET_HASH>'; // base64, from Settings in the merchant console
$ivHex = '<IV_HEX>'; // 32 hex characters, from the API documentation page in the console
function encryptFields(array $fields, string $secretHash, string $ivHex): string
{
// With options = 0, openssl_encrypt applies PKCS#7 padding and returns base64.
return openssl_encrypt(json_encode($fields), 'aes-256-cbc', base64_decode($secretHash), 0, hex2bin($ivHex));
}
$fields = [
'merchant_order_id' => '<merchant_order_id>',
'first_name' => '<first_name>',
'last_name' => '<last_name>',
'email' => '<email>',
'phone_number' => '<phone_number>',
'amount' => '<amount>',
'membership_duration' => '<membership_duration>',
'response_url' => '<response_url>',
'webhook_url' => '<webhook_url>',
];
$ch = curl_init($baseUrl . '/v2/upi/payin');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['encrypted_data' => encryptFields($fields, $secretHash, $ivHex)]),
]);
$data = json_decode(curl_exec($ch), true);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (($data['status'] ?? null) === 'authenticate') {
// Created and waiting for the customer: redirect to authenticate_url, or show upi_intent as a QR code.
echo $data['transaction_id'], ' ', $data['authenticate_url'], PHP_EOL;
} else {
echo $httpCode, ' ', $data['status'] ?? '', ' ', $data['message'] ?? '', PHP_EOL;
}