MyTPE is getting a fresh new look! A simpler, faster platform is on the way. Learn more →
MyTPEMyTPE Pay
Pay Links

Create Pay Link

Create a new payment link with specific validity modes.

POST https://dev.mytpe.app/api/v2/mytpe-pay/links/store

Creates a new payment link for a Mytpe Pay instance.

Authentication: API Key (X-API-KEY + X-API-SECRET headers)

This API requires authentication via X-API-KEY and X-API-SECRET headers.

It is critical to understand the relationship between type and payment_mode.

  • Dynamic Links: You set a fixed price that the customer must pay. These support all 3 modes (reusable, one_shot, limited).
  • Static Links: The customer enters the amount (e.g., Donation). These are always Reusable. You cannot set them to one_shot or limited.

Validation Rule

If you send type: "static", the system will ignore any payment_mode or max_payments values you provide. These parameters are only respected when type is dynamic.

Validity & Modes (Dynamic Only)

For Dynamic links, you can define the lifecycle:

  • reusable (Default): The link never expires. Ideal for e-commerce products.
  • one_shot: The link accepts exactly 1 payment and auto-closes. Ideal for specific invoices.
  • limited: The link accepts a quota defined by max_payments. Ideal for event tickets.

Body Parameters

Prop

Type

Settings

You can configure link behavior via the settings object. All settings have sensible defaults — you only need to include the ones you want to override.

Prop

Type

Form-data format

settings[show_retry_button]:false
settings[allow_anonymous]:true

Default Behavior

If you don't provide settings, all defaults apply:

  • show_retry_button defaults to true
  • allow_anonymous defaults to false (payer details are required)

Example: Disable Retry Button

To create a link where the customer cannot retry payment after a failure:

const form = new FormData();
form.append('mytpe_pay_id', 'mytpe_pay_xyz789');
form.append('title', 'One-time Invoice');
form.append('details', 'Invoice #1234');
form.append('type', 'dynamic');
form.append('amount', '5000.00');
form.append('slug', 'invoice-1234');
form.append('payment_mode', 'one_shot');
form.append('settings[show_retry_button]', 'false');

const response = await fetch('https://dev.mytpe.app/api/v2/mytpe-pay/links/store', {
  method: 'POST',
  headers: {
    'X-API-KEY': 'your_api_key',
    'X-API-SECRET': 'your_api_secret',
  },
  body: form,
});

When a payment fails on this link, the results page will not show a "Retry" button — the customer will only see the "Home" button.

Example: Allow Anonymous Payments

To create a donation-style link where customers can pay without revealing identity:

const form = new FormData();
form.append('mytpe_pay_id', 'mytpe_pay_xyz789');
form.append('title', 'Donate to our cause');
form.append('details', 'Anonymous donations welcome.');
form.append('type', 'static');
form.append('slug', 'donate');
form.append('settings[allow_anonymous]', 'true');

const response = await fetch('https://dev.mytpe.app/api/v2/mytpe-pay/links/store', {
  method: 'POST',
  headers: {
    'X-API-KEY': 'your_api_key',
    'X-API-SECRET': 'your_api_secret',
  },
  body: form,
});

The checkout page will display an "Anonymous payment" checkbox. When checked, the name, email, and phone fields are hidden and the transaction is recorded with no payer details.

Privacy Trade-off

Anonymous payments are useful for donations, charities, or sensitive transactions, but you will not be able to send payment receipts or contact the payer. Use only when payer identification is not required.

Inactivity Expiry

What is inactivity expiry?

A one_shot or limited link can auto-close (status → expired) if it doesn't receive any successful payment within a configurable window. This is useful for time-sensitive use cases like delivery payments, ticket reservations, or limited promotions.

The timeout is resolved with a 2-level fallback chain:

  1. Per-link overrideinactivity_timeout_minutes on this request
  2. Instance defaultdefault_inactivity_timeout_minutes on the parent Mytpe Pay instance (Update Instance)
  3. Otherwise — the link never inactivity-expires (only the existing max_payments quota can close it)

Rules

  • Unit: minutes (integer, range 1–525600 = 1 minute to 1 year).
  • Modes: only one_shot and limited. Reusable links never inactivity-expire — sending this field on a reusable link is ignored.
  • Activity definition: the clock resets only on successful payments (status completed). Pending/failed/refused transactions do not reset the clock. The clock starts at link creation.
  • Resulting status: when a link auto-expires, its status becomes expired and a pay_link.expired webhook fires (see Webhook Events).
  • Reactivation: expired links are terminal — they cannot be reactivated. Create a new link instead.

Form-data format

inactivity_timeout_minutes:2880

(2880 minutes = 48 hours)

Example: 30-minute reservation hold

A "Reserve seat" link that auto-expires if the customer doesn't complete payment within 30 minutes:

const form = new FormData();
form.append('mytpe_pay_id', 'mytpe_pay_xyz789');
form.append('title', 'Seat reservation #A12');
form.append('details', 'Hold expires in 30 minutes if unpaid.');
form.append('type', 'dynamic');
form.append('amount', '1500.00');
form.append('slug', 'seat-a12-reservation');
form.append('payment_mode', 'one_shot');
form.append('inactivity_timeout_minutes', '30');

const response = await fetch('https://dev.mytpe.app/api/v2/mytpe-pay/links/store', {
  method: 'POST',
  headers: {
    'X-API-KEY': 'your_api_key',
    'X-API-SECRET': 'your_api_secret',
  },
  body: form,
});
form.append('inactivity_timeout_minutes', '2880'); // 48h × 60 = 2880

Example: Inherit from instance default

Simply omit inactivity_timeout_minutes from the request body. The link will use whatever default the parent instance has configured (or never expire if the instance has no default either).

User-Defined Fields (UDFs)

New Feature

UDFs let you attach custom data to every payment made through a link. This data is sent to the payment gateway and appears on the transaction receipt — useful for tracking invoices, customer references, or any business-specific identifier.

You can attach up to 5 UDFs (udf1 through udf5) to a payment link. Each UDF has:

  • value (required): The data you want to attach (max 20 alphanumeric characters — letters and numbers only, no special characters or spaces).
  • label (optional): A human-readable name displayed on the receipt (max 50 characters).

udf1 — Bank Receipt Field

udf1 is special: its value is displayed on the bank payment receipt, making it ideal for reconciliation references (e.g., invoice number, order ID). If you don't set udf1, the system auto-generates an order number for it. The other UDFs (udf2udf5) are only stored on the SATIM platform and MyTPE — they do not appear on the receipt.

When a customer pays on this link, the UDFs are automatically forwarded to the payment gateway.

UDF Format (form-data)

udfs[udf1][value]:INV2026001
udfs[udf1][label]:Invoice Number
udfs[udf2][value]:JohnDoe
udfs[udf2][label]:Customer Name

UDF Parameters

Prop

Type

Example Use Cases

  • E-commerce: Attach an invoice number and customer name to every payment.
  • Events: Attach a ticket ID and seat number.
  • Services: Attach a contract reference and service date.

Example Request

create-pay-link.js
const form = new FormData();
form.append('mytpe_pay_id', 'mytpe_pay_xyz789');
form.append('title', 'Limited Offer Product');
form.append('details', 'A special limited edition product.');
form.append('type', 'dynamic');
form.append('amount', '5000.00');
form.append('slug', 'limited-offer');
form.append('payment_mode', 'limited');
form.append('max_payments', '50');
form.append('metadata[internal_id]', 'PROD_123');
form.append('udfs[udf1][value]', 'INV2026001');
form.append('udfs[udf1][label]', 'Invoice Number');
form.append('udfs[udf2][value]', 'JohnDoe');
form.append('udfs[udf2][label]', 'Customer Name');
form.append('logo', document.querySelector('#logo').files[0]);

const response = await fetch('https://dev.mytpe.app/api/v2/mytpe-pay/links/store', {
  method: 'POST',
  headers: {
    'X-API-KEY': 'your_api_key',
    'X-API-SECRET': 'your_api_secret',
  },
  body: form,
});

const data = await response.json();
console.log(data);
create-pay-link.ts
interface PayLinkResponse {
  success: boolean;
  message: string;
  data: {
    id: string;
    title: string;
    slug: string;
    type: string;
    amount: string;
    status: string;
    payment_mode: string;
    max_payments: number | null;
    payment_link: string;
    payment_status: string;
  };
}

const form = new FormData();
form.append('mytpe_pay_id', 'mytpe_pay_xyz789');
form.append('title', 'Limited Offer Product');
form.append('details', 'A special limited edition product.');
form.append('type', 'dynamic');
form.append('amount', '5000.00');
form.append('slug', 'limited-offer');
form.append('payment_mode', 'limited');
form.append('max_payments', '50');
form.append('metadata[internal_id]', 'PROD_123');
form.append('udfs[udf1][value]', 'INV2026001');
form.append('udfs[udf1][label]', 'Invoice Number');
form.append('udfs[udf2][value]', 'JohnDoe');
form.append('udfs[udf2][label]', 'Customer Name');

const fileInput = document.querySelector<HTMLInputElement>('#logo');
if (fileInput?.files?.[0]) {
  form.append('logo', fileInput.files[0]);
}

const response = await fetch('https://dev.mytpe.app/api/v2/mytpe-pay/links/store', {
  method: 'POST',
  headers: {
    'X-API-KEY': 'your_api_key',
    'X-API-SECRET': 'your_api_secret',
  },
  body: form,
});

const data: PayLinkResponse = await response.json();
console.log(data);
create_pay_link.py
import requests

url = 'https://dev.mytpe.app/api/v2/mytpe-pay/links/store'

headers = {
    'X-API-KEY': 'your_api_key',
    'X-API-SECRET': 'your_api_secret',
}

payload = {
    'mytpe_pay_id': 'mytpe_pay_xyz789',
    'title': 'Limited Offer Product',
    'details': 'A special limited edition product.',
    'type': 'dynamic',
    'amount': '5000.00',
    'slug': 'limited-offer',
    'payment_mode': 'limited',
    'max_payments': '50',
    'metadata[internal_id]': 'PROD_123',
    'udfs[udf1][value]': 'INV2026001',
    'udfs[udf1][label]': 'Invoice Number',
    'udfs[udf2][value]': 'JohnDoe',
    'udfs[udf2][label]': 'Customer Name',
}

files = {
    'logo': ('logo.png', open('/path/to/logo.png', 'rb'), 'image/png'),
}

response = requests.post(url, headers=headers, data=payload, files=files)

data = response.json()
print(data)
CreatePayLinkController.php
use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'X-API-KEY' => 'your_api_key',
    'X-API-SECRET' => 'your_api_secret',
])
->attach('logo', file_get_contents('/path/to/logo.png'), 'logo.png')
->post('https://dev.mytpe.app/api/v2/mytpe-pay/links/store', [
    'mytpe_pay_id' => 'mytpe_pay_xyz789',
    'title' => 'Limited Offer Product',
    'details' => 'A special limited edition product.',
    'type' => 'dynamic',
    'amount' => '5000.00',
    'slug' => 'limited-offer',
    'payment_mode' => 'limited',
    'max_payments' => 50,
    'metadata' => ['internal_id' => 'PROD_123'],
    'udfs' => [
        'udf1' => ['value' => 'INV2026001', 'label' => 'Invoice Number'],
        'udf2' => ['value' => 'JohnDoe', 'label' => 'Customer Name'],
    ],
]);

$data = $response->json();
create-pay-link.php
<?php

$curl = curl_init();

$boundary = uniqid();
$delimiter = '-------------' . $boundary;

$postData = '';
$fields = [
    'mytpe_pay_id' => 'mytpe_pay_xyz789',
    'title' => 'Limited Offer Product',
    'details' => 'A special limited edition product.',
    'type' => 'dynamic',
    'amount' => '5000.00',
    'slug' => 'limited-offer',
    'payment_mode' => 'limited',
    'max_payments' => '50',
    'metadata[internal_id]' => 'PROD_123',
    'udfs[udf1][value]' => 'INV2026001',
    'udfs[udf1][label]' => 'Invoice Number',
    'udfs[udf2][value]' => 'JohnDoe',
    'udfs[udf2][label]' => 'Customer Name',
];

foreach ($fields as $name => $value) {
    $postData .= "--{$delimiter}\r\n";
    $postData .= "Content-Disposition: form-data; name=\"{$name}\"\r\n\r\n";
    $postData .= "{$value}\r\n";
}

$postData .= "--{$delimiter}\r\n";
$postData .= "Content-Disposition: form-data; name=\"logo\"; filename=\"logo.png\"\r\n";
$postData .= "Content-Type: image/png\r\n\r\n";
$postData .= file_get_contents('/path/to/logo.png') . "\r\n";
$postData .= "--{$delimiter}--\r\n";

curl_setopt_array($curl, [
    CURLOPT_URL => 'https://dev.mytpe.app/api/v2/mytpe-pay/links/store',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => $postData,
    CURLOPT_HTTPHEADER => [
        'X-API-KEY: your_api_key',
        'X-API-SECRET: your_api_secret',
        'Content-Type: multipart/form-data; boundary=' . $delimiter,
    ],
]);

$response = curl_exec($curl);
curl_close($curl);

echo $response;
create-pay-link.cjs
const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

const form = new FormData();
form.append('mytpe_pay_id', 'mytpe_pay_xyz789');
form.append('title', 'Limited Offer Product');
form.append('details', 'A special limited edition product.');
form.append('type', 'dynamic');
form.append('amount', '5000.00');
form.append('slug', 'limited-offer');
form.append('payment_mode', 'limited');
form.append('max_payments', '50');
form.append('metadata[internal_id]', 'PROD_123');
form.append('udfs[udf1][value]', 'INV2026001');
form.append('udfs[udf1][label]', 'Invoice Number');
form.append('udfs[udf2][value]', 'JohnDoe');
form.append('udfs[udf2][label]', 'Customer Name');
form.append('logo', fs.createReadStream('/path/to/logo.png'));

axios.post('https://dev.mytpe.app/api/v2/mytpe-pay/links/store', form, {
  headers: {
    ...form.getHeaders(),
    'X-API-KEY': 'your_api_key',
    'X-API-SECRET': 'your_api_secret',
  },
})
.then(response => console.log(response.data))
.catch(error => console.error(error.response?.data));

Example Success Response (201 Created)

201 Created

{
    "success": true,
    "message": "Pay link created successfully",
    "data": {
        "mytpe_pay_id": "instance-001",
        "title": "Demo Product",
        "details": "A detailed description of the demo product.",
        "slug": "demo-product",
        "type": "dynamic",
        "amount": "2500.00",
        "status": "active",
        "logo": null,
        "payment_mode": "reusable",
        "max_payments": null,
        "successful_payments_count": 0,
        "metadata": {
            "internal_reference": "REF-123"
        },
        "udfs": {
            "udf1": { "value": "INV2026001", "label": "Invoice Number" },
            "udf2": { "value": "JohnDoe", "label": "Customer Name" }
        },
        "settings": {
            "show_retry_button": true,
            "allow_anonymous": false
        },
        "id": "link-demo-123",
        "updated_at": "2026-01-17T10:00:00.000Z",
        "created_at": "2026-01-17T10:00:00.000Z",
        "payment_link": "https://frontdev.mytpe.app/pay/demo-product",
        "payment_status": "active",
        "remaining_payments": null
    }
}

Understanding payment_status

Newly created links will have one of these payment_status values:

  • active: Reusable or limited links ready to accept payments
  • awaiting_payment: One-shot links waiting for their first payment

The payment_status field automatically updates based on payment activity and helps you track the payment state of each link.

Error Responses

422 Validation Error

{
    "message": "The given data was invalid.",
    "errors": {
        "slug": ["The slug has already been taken."],
        "amount": ["The amount field is required when type is dynamic."]
    }
}

On this page