If you still call
POST /api/v1/payin, that endpoint is deprecated. Use POST /api/v2/payin for all new integrations.Overview
The PayIn API for Peru enables customer deposit transactions through multiple payment methods including bank transfers, QR code payments, and PagoEfectivo cash vouchers. Transactions are processed in real-time with webhook notifications for status updates.Key Features
- Multiple Payment Methods:
BANK_TRANSFER,QR,PAGOEFECTIVO - Currency: Peruvian Sol (
PEN) - Real-time Processing: Immediate transaction validation and processing
- Webhook Notifications: Asynchronous status updates via IPN
- Secure Redirect Flow: Customer completes payment on secure hosted page
Quick Reference
| Parameter | Value | Description |
|---|---|---|
| Endpoint | POST /api/v2/payin | Initiate PayIn transaction |
| Currency | PEN | Peruvian Sol |
| Country Code | PE | Peru |
| Auth Required | Yes | Token-Top + Basic Auth |
Payment experiences (v2)
payment_method | Mode | payment_url | payment_information |
|---|---|---|---|
BANK_TRANSFER | Checkout | β Hosted bank list | β |
CASH | Checkout | β Hosted cash flow | β |
QR | Hybrid | β | β
QR data (qr_code_base_64, expiration_date, β¦) |
CARD | Checkout | β | β |
ALL_METHODS | Checkout | β | β |
QR returns payment instructions in
payment_information at creation time and also provides payment_url as a hosted fallback. Maximum amount: 500 PEN. All dates are UTC (Y-m-d H:i:s).Response fields by method
payment_method | Mode | payment_url | payment_information |
|---|---|---|---|
BANK_TRANSFER, CASH, CARD, ALL_METHODS | Checkout | β
payments/main | Not returned |
QR | Hybrid | β
payments/main | β
qr_code_base_64, expiration_date (UTC) |
QR β Hybrid
curl --request POST 'https://api-empresas.staging.topup.com.co/production/api/v2/payin' \
--header 'Token-Top: your_auth_token' \
--header 'Authorization: Basic your_auth_key' \
--header 'Content-Type: application/json' \
--data-raw '{
"reference": "qr-ref-001",
"amount": 100,
"currency": "PEN",
"country": "PE",
"payment_method": "QR",
"description": "QR payment",
"customer_data": {
"legal_doc": "87654321",
"legal_doc_type": "DNI",
"phone_code": "51",
"phone_number": "912345678",
"email": "customer@example.com",
"full_name": "Carlos Rodriguez"
},
"ipn_url": "https://your-domain.com/webhook",
"redirect_url": "https://your-domain.com/success"
}'
{
"code": "01",
"status": "SUCCESS",
"message": "Operacion exitosa",
"data": {
"ticket": "qr-ticket-id",
"date": "2025-10-15 15:44:57",
"payment_url": "https://link.staging.topup.com.co/payments/main?s=qr-ticket-id",
"payment_information": {
"qr_code_base_64": "iVBORw0KGgo...",
"expiration_date": "2025-10-16 03:44:57"
},
"transaction": {
"reference": "qr-ref-001",
"amount": 100,
"currency": "PEN",
"payment_method": "QR",
"redirect_url": "https://your-domain.com/success",
"ipn_url": "https://your-domain.com/webhook",
"description": "QR payment"
}
}
}
payment_information field | Description |
|---|---|
qr_code_base_64 | QR image as base64 (render in your UI) |
expiration_date | QR validity end in UTC |
QR amount limits
| Limit | Default | Description |
|---|---|---|
| Maximum transaction amount (Peru) | 500 PEN | QR provider cap for Peru. Checked during request validation before the transaction is created. |
amount is greater than 500 PEN, the API responds with HTTP 422 and does not create a transaction:
{
"code": "00",
"status": false,
"error": "VALIDATION_ERROR",
"message": "El monto mΓ‘ximo permitido para pagos QR es de 500 PEN"
}
| Field | Description |
|---|---|
error | "VALIDATION_ERROR" β request failed validation |
message | Human-readable reason (amount exceeded the QR maximum) |
Amounts above 500 PEN are rejected at validation time. Adjust
amount before calling POST /api/v2/payin with payment_method: "QR".BANK_TRANSFER / CASH β Checkout
Mode: Checkout Β· You get:payment_url only (no payment_information).
Send the method without extra fields. Redirect the customer to data.payment_url.
{
"code": "01",
"status": "SUCCESS",
"message": "Operacion exitosa",
"data": {
"ticket": "pe-checkout-ticket",
"date": "2025-10-15 15:44:57",
"payment_url": "https://link.staging.topup.com.co/payments/main?s=pe-checkout-ticket",
"transaction": {
"reference": "bank-transfer-ref-001",
"amount": 150,
"currency": "PEN",
"payment_method": "BANK_TRANSFER",
"redirect_url": "https://your-domain.com/success",
"ipn_url": "https://your-domain.com/webhook",
"description": "Bank transfer"
}
}
}
Required Fields
| Field | Type | Description | Example |
|---|---|---|---|
reference | string | Unique transaction identifier from your system | "13cNPNGbX7meiMppXzVz7g781ysektqq5X" |
amount | float | Transaction amount in PEN | 1.50 (= S/ 1.50 PEN) |
currency | string | Three-letter currency code (ISO 4217) | "PEN" |
country | string | Two-letter country code (ISO 3166-1 alpha-2) | "PE" |
payment_method | string | Payment method identifier | "ALL_METHODS","CARD" |
ipn_url | string | Webhook URL for transaction status updates | "https://your-domain.com/webhook" |
redirect_url | string | URL to redirect customer after payment | "https://your-domain.com/payment/success" |
customer_data | object | Customer information object (see below) | - |
Customer Data Object
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
legal_doc | string | β | Customerβs legal document number | "12345678" |
legal_doc_type | string | β | Document type: DNI, RUC, CE, PPN | "DNI" |
phone_code | string | β | Country calling code | "51" |
phone_number | string | β | Phone number without country code | "987654321" |
email | string | β | Customer email address | "johndoe@email.com" |
full_name | string | β | Customer full name | "John Doe" |
Optional Fields
| Field | Type | Description | Default |
|---|---|---|---|
description | string | Transaction description | - |
expiration_time | integer | Payment link expiration in minutes | 720 (12 hours) |
QR Code Direct Flow (deprecated)
This flow applied to PayIn v1 (
POST /api/v1/payin) with the ExperienciaQrCode flag enabled in TumiPay backoffice configuration. It is deprecated β use QR Hybrid (v2) with POST /api/v2/payin instead. Contact integrations if you still rely on the v1 flag.payment_url to redirect the customer), the v1 QR direct flow generated the QR code at transaction creation and returned it as a base64 image in the response. The merchant was responsible for rendering and displaying it.
v1 vs v2 response shape
| Aspect | v1 (deprecated) | v2 (recommended) |
|---|---|---|
| Endpoint | POST /api/v1/payin | POST /api/v2/payin |
| Backoffice flag | ExperienciaQrCode required | Not required |
| QR data location | data.qr_code_base_64 | data.payment_information.qr_code_base_64 |
| Hosted fallback | data.payment_url (optional) | data.payment_url (hybrid) |
How it worked (v1)
Merchant TumiPay API StreamPayments
| | |
|-- POST /api/v1/payin ----β | |
| payment_method: "QR" | |
| country: "PE" |-- Create QR Order ----β |
| |β-- qrCode (string) ---- |
|β-- 200 OK (qr_code_base64) | |
| [display QR to customer] | |
| Customer scans QR with bank app |
|β-- Webhook (ipn_url) ----- | |
QR amount limits (v1)
| Limit | Default | Description |
|---|---|---|
| Maximum transaction amount (Peru) | 500 PEN | QR provider cap. Returned QR_AMOUNT_LIMIT_EXCEEDED if exceeded. |
Request example (v1 β QR direct)
The request was identical to the standard flow; the only change waspayment_method: "QR":
curl --request POST 'https://api-empresas.staging.topup.com.co/production/api/v1/payin' \
--header 'Token-Top: your_auth_token' \
--header 'Authorization: Basic your_auth_key' \
--header 'Content-Type: application/json' \
--data-raw '{
"reference": "unique-ref-qr-001",
"amount": 50,
"currency": "PEN",
"country": "PE",
"payment_method": "QR",
"description": "Pago QR directo",
"customer_data": {
"legal_doc": "12345678",
"legal_doc_type": "DNI",
"phone_code": "51",
"phone_number": "987654321",
"email": "johndoe@email.com",
"full_name": "John Doe"
},
"expiration_time": 30,
"ipn_url": "https://your-domain.com/webhook",
"redirect_url": "https://your-domain.com/payment/success"
}'
Response example (v1 β QR direct)
The response included all standard fields plusqr_code_base_64 directly inside data (not in payment_information):
| Field | Type | Description |
|---|---|---|
data.ticket | string | Unique transaction identifier |
data.date | string | Creation timestamp (YYYY-MM-DD HH:MM:SS) |
data.payment_url | string | Hosted payment page (fallback) |
data.qr_code_base_64 | string | QR image in base64 (PNG) |
data.transaction | object | Echo of request metadata |
{
"code": "01",
"status": "SUCCESS",
"message": "Operacion exitosa",
"data": {
"ticket": "3f7a9b2c-1d4e-5f6a-7b8c-9d0e1f2a3b4c",
"date": "2026-05-11 14:30:00",
"payment_url": "https://link.staging.topup.com.co/payments/main?s=3f7a9b2c-1d4e-5f6a-7b8c-9d0e1f2a3b4c",
"qr_code_base_64": "iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAA...",
"transaction": {
"reference": "unique-ref-qr-001",
"amount": 50,
"currency": "PEN",
"payment_method": "QR",
"redirect_url": "https://your-domain.com/payment/success",
"ipn_url": "https://your-domain.com/webhook",
"description": "Pago QR directo"
}
}
}
Render the QR (v1)
<img src="data:image/png;base64,{{ qr_code_base_64 }}" alt="Scan to pay" />
<img src={`data:image/png;base64,${data.qr_code_base_64}`} alt="Scan to pay" />
data.payment_information.qr_code_base_64 instead β see Payment experiences (v2).
QR-specific errors (v1)
| HTTP | code | error | Description |
|---|---|---|---|
| 422 | "00" | QR_AMOUNT_LIMIT_EXCEEDED | Amount exceeds the QR maximum for Peru |
| 400 | "00" | TRANSACTION_IN_PROGRESS | Active transaction already exists for that email/amount |
| 200 | "00" | β | QR generation failed at provider (retry) |
ipn_url. See Webhooks for the payload structure.
Response Structure
Success Response
| Field | Type | Description |
|---|---|---|
code | string | Response code ("01" = success) |
status | string | Transaction status ("SUCCESS") |
message | string | Description of the response |
data | object | Transaction data object |
Data Object
| Field | Type | Description |
|---|---|---|
ticket | string | Unique transaction identifier (TumiPay ID) |
date | string | Transaction creation time in UTC (YYYY-MM-DD HH:MM:SS) |
payment_url | string | Secure payment URL for customer redirect (Checkout / Hybrid) |
payment_information | object | Provider instructions (Direct / Hybrid only β omitted in Checkout) |
transaction | object | Transaction details echo |
Examples
Request Example
cURL
curl --request POST 'https://api-empresas.staging.topup.com.co/production/api/v2/payin' \
--header 'Token-Top: your_auth_token' \
--header 'Authorization: Basic your_auth_key' \
--header 'Content-Type: application/json' \
--data-raw '{
"reference": "13cNPNGbX7meiMppXzVz7g781ysektqq5X",
"amount": 150,
"currency": "PEN",
"country": "PE",
"payment_method": "ALL_METHODS",
"description": "Test PayIn",
"customer_data": {
"legal_doc": "12345678",
"legal_doc_type": "DNI",
"phone_code": "51",
"phone_number": "987654321",
"email": "johndoe@email.com",
"full_name": "John Doe"
},
"expiration_time": 720,
"ipn_url": "https://your-domain.com/webhook",
"redirect_url": "https://your-domain.com/payment/success"
}'
use reqwest::Client;
use serde_json::json;
async fn make_payin_request() -> Result<(), reqwest::Error> {
let client = Client::new();
let response = client.post("https://api-empresas.staging.topup.com.co/production/api/v2/payin")
.header("Token-Top", "your_auth_token")
.header("Authorization", "Basic your_auth_key")
.header("Content-Type", "application/json")
.json(&json!({
"reference": "13cNPNGbX7meiMppXzVz7g781ysektqq5X",
"amount": 150,
"currency": "PEN",
"country": "PE",
"payment_method": "ALL_METHODS",
"description": "Test PayIn",
"customer_data": {
"legal_doc": "12345678",
"legal_doc_type": "DNI",
"phone_code": "51",
"phone_number": "987654321",
"email": "johndoe@email.com",
"full_name": "John Doe"
},
"expiration_time": 720,
"ipn_url": "https://your-domain.com/webhook",
"redirect_url": "https://your-domain.com/payment/success"
}))
.send()
.await?;
let response_json: serde_json::Value = response.json().await?;
println!("{:#?}", response_json);
Ok(())
}
import axios from 'axios';
const makePayinRequest = async () => {
try {
const response = await axios.post('https://api-empresas.staging.topup.com.co/production/api/v2/payin', {
reference: '13cNPNGbX7meiMppXzVz7g781ysektqq5X',
amount: 150,
currency: 'PEN',
country: 'PE',
payment_method: 'ALL_METHODS',
description: 'Test PayIn',
customer_data: {
legal_doc: '12345678',
legal_doc_type: 'DNI',
phone_code: '51',
phone_number: '987654321',
email: 'johndoe@email.com',
full_name: 'John Doe'
},
expiration_time: 720,
ipn_url: 'https://your-domain.com/webhook',
redirect_url: 'https://your-domain.com/payment/success'
}, {
headers: {
'Token-Top': 'your_auth_token',
'Authorization': 'Basic your_auth_key',
'Content-Type': 'application/json'
}
});
console.log(response.data);
} catch (error) {
console.error(error);
}
};
makePayinRequest();
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api-empresas.staging.topup.com.co/production/api/v2/payin',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS =>'{
"reference": "13cNPNGbX7meiMppXzVz7g781ysektqq5X",
"amount": 150,
"currency": "PEN",
"country": "PE",
"payment_method": "ALL_METHODS",
"description": "Test PayIn",
"customer_data": {
"legal_doc": "12345678",
"legal_doc_type": "DNI",
"phone_code": "51",
"phone_number": "987654321",
"email": "johndoe@email.com",
"full_name": "John Doe"
},
"expiration_time": 720,
"ipn_url": "https://your-domain.com/webhook",
"redirect_url": "https://your-domain.com/payment/success"
}',
CURLOPT_HTTPHEADER => array(
'Token-Top: your_auth_token',
'Authorization: Basic your_auth_key',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.io.IOException;
public class PayinRequest {
public static void main(String[] args) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
String json = "{ \\"reference\\": \\"13cNPNGbX7meiMppXzVz7g781ysektqq5X\\", \\"amount\\": 150, \\"currency\\": \\"PEN\\", \\"country\\": \\"PE\\", \\"payment_method\\": \\"ALL_METHODS\\", \\"description\\": \\"Test PayIn\\", \\"customer_data\\": { \\"legal_doc\\": \\"12345678\\", \\"legal_doc_type\\": \\"DNI\\", \\"phone_code\\": \\"51\\", \\"phone_number\\": \\"987654321\\", \\"email\\": \\"johndoe@email.com\\", \\"full_name\\": \\"John Doe\\" }, \\"expiration_time\\": 720, \\"ipn_url\\": \\"https://your-domain.com/webhook\\", \\"redirect_url\\": \\"https://your-domain.com/payment/success\\" }";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api-empresas.staging.topup.com.co/production/api/v2/payin"))
.header("Token-Top", "your_auth_token")
.header("Authorization", "Basic your_auth_key")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
package main
import (
"fmt"
"net/http"
"io/ioutil"
"strings"
)
func main() {
url := "https://api-empresas.staging.topup.com.co/production/api/v2/payin"
method := "POST"
payload := strings.NewReader(`{
"reference": "13cNPNGbX7meiMppXzVz7g781ysektqq5X",
"amount": 150,
"currency": "PEN",
"country": "PE",
"payment_method": "ALL_METHODS",
"description": "Test PayIn",
"customer_data": {
"legal_doc": "12345678",
"legal_doc_type": "DNI",
"phone_code": "51",
"phone_number": "987654321",
"email": "johndoe@email.com",
"full_name": "John Doe"
},
"expiration_time": 720,
"ipn_url": "https://your-domain.com/webhook",
"redirect_url": "https://your-domain.com/payment/success"
}`)
client := &http.Client {}
req, err := http.NewRequest(method, url, payload)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Token-Top", "your_auth_token")
req.Header.Add("Authorization", "Basic your_auth_key")
req.Header.Add("Content-Type", "application/json")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
Response Example
{
"code": "01",
"status": "SUCCESS",
"message": "Operacion exitosa",
"data": {
"ticket": "8BNsCFva1NKPqy2",
"date": "2025-10-15 17:58:36",
"payment_url": "https://link.staging.topup.com.co/payments/main?s=8BNsCFva1NKPqy2",
"transaction": {
"reference": "13cNPNGbX7meiMppXzVz7g781ysektqq5X",
"amount": 5000,
"currency": "PEN",
"payment_method": "ALL_METHODS",
"redirect_url": "https://your-domain.com/payment/success",
"ipn_url": "https://your-domain.com/webhook",
"description": "Test PayIn"
}
}
}