Create Payment Request

Overview

Payment requests are the core of Unter's payment system. When you create a payment request, you're asking someone to pay a specific amount of a cryptocurrency token to a designated wallet address. Each payment request gets a unique payment URL that you can share with your customers.

Endpoint

POST /api/payment-requests

Required Permission: create_payments

Request Body

Required Fields

Field
Type
Description
Example

amount

string

The amount to request in the token's smallest unit (wei-like format)

"1000000" (1 USDC with 6 decimals)

token_id

integer

The ID of the token to receive

1

chain_id

integer

The ID of the blockchain network

1

recipient_address

string

The wallet address to receive the payment

"0x742C4B8C2515Ad1D8e1C71e8e24c8D5F9A1a8B5A"

Optional Fields

Field
Type
Description
Example

external_id

string

Your internal reference ID for this payment request (max 255 chars)

"order_12345"

description

string

Description of what this payment is for (max 1000 chars)

"Payment for premium subscription"

redirect_url

string

URL to redirect to after successful payment (max 500 chars)

"https://yourapp.com/payment/success"

cancel_url

string

URL to redirect to if payment is cancelled (max 500 chars)

"https://yourapp.com/payment/cancel"

expires_at

string (ISO 8601)

When this payment request expires (max 30 days from now)

"2024-12-31T23:59:59Z"

metadata

object

Additional key-value data for the payment request

{"customer_id": "12345", "product": "premium"}

Understanding Amounts

Cryptocurrency amounts are handled in their smallest unit (similar to how cents relate to dollars):

  • USDC (6 decimals): "1000000" = $1.00 USDC

  • ETH (18 decimals): "1000000000000000000" = 1.0 ETH

  • USDT (6 decimals): "500000" = $0.50 USDT

The amount must be a string containing only digits, with no decimal points or commas.

Address Validation

Recipient addresses are validated based on the blockchain type:

Ethereum-compatible chains (EVM)

  • Must be 42 characters long

  • Must start with 0x

  • Example: 0x742C4B8C2515Ad1D8e1C71e8e24c8D5F9A1a8B5A

Solana

  • Must use Base58 encoding

  • Example: 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM

Automatic Behavior

When you create a payment request, several things happen automatically:

  1. Unique identifiers are generated:

    • public_id: A UUID for internal tracking

    • shortcode: An 8-character code for the payment URL (e.g., A4B2C8XZ)

  2. Default expiration is set:

    • If you don't specify expires_at, it defaults to 24 hours from creation

  3. Payment URL is created:

    • Format: https://unter.tech/pay/{shortcode}

    • This is where customers go to complete payment

Example Requests

Minimal Request

curl -X POST https://api.unter.tech/api/payment-requests \
  -H "X-API-Key: unter_YOUR_API_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "2500000",
    "token_id": 1,
    "chain_id": 1,
    "recipient_address": "0x742C4B8C2515Ad1D8e1C71e8e24c8D5F9A1a8B5A"
  }'

Full Request with All Options

curl -X POST https://api.unter.tech/api/payment-requests \
  -H "X-API-Key: unter_YOUR_API_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "5000000",
    "token_id": 1,
    "chain_id": 1,
    "recipient_address": "0x742C4B8C2515Ad1D8e1C71e8e24c8D5F9A1a8B5A",
    "external_id": "invoice_789",
    "description": "Pro Plan Subscription - Monthly",
    "redirect_url": "https://myapp.com/success?order=789",
    "cancel_url": "https://myapp.com/cancelled?order=789",
    "expires_at": "2024-08-15T18:00:00Z",
    "metadata": {
      "customer_id": "cust_abc123",
      "plan": "pro",
      "billing_cycle": "monthly"
    }
  }'

Success Response (201 Created)

{
  "data": {
    "public_id": "550e8400-e29b-41d4-a716-446655440000",
    "shortcode": "A4B2C8XZ",
    "amount": {
      "raw": "5000000",
      "formatted": "5.00",
      "decimals": 6
    },
    "token": {
      "id": 1,
      "symbol": "USDC",
      "name": "USD Coin",
      "decimals": 6
    },
    "chain": {
      "id": 1,
      "slug": "ethereum",
      "name": "Ethereum"
    },
    "recipient_address": "0x742C4B8C2515Ad1D8e1C71e8e24c8D5F9A1a8B5A",
    "status": "active",
    "description": "Pro Plan Subscription - Monthly",
    "external_id": "invoice_789",
    "payment_url": "https://pay.unter.tech/pay/A4B2C8XZ",
    "redirect_url": "https://myapp.com/success?order=789",
    "cancel_url": "https://myapp.com/cancelled?order=789",
    "paid_amount": {
      "raw": "0",
      "formatted": "0.00",
      "decimals": 6
    },
    "remaining_amount": {
      "raw": "5000000",
      "formatted": "5.00",
      "decimals": 6
    },
    "payment_attempts": 0,
    "expires_at": "2024-08-15T18:00:00.000Z",
    "created_at": "2024-08-14T12:30:00.000Z",
    "completed_at": null,
    "metadata": {
      "customer_id": "cust_abc123",
      "plan": "pro",
      "billing_cycle": "monthly"
    },
    "payments": []
  }
}

Code Examples

JavaScript (Node.js)

const axios = require('axios');

const apiKey = process.env.UNTER_API_KEY;

const client = axios.create({
  baseURL: 'https://api.unter.tech/api',
  headers: {
    'X-API-Key': apiKey,
    'Content-Type': 'application/json'
  }
});

async function createPaymentRequest(data) {
  try {
    const response = await client.post('/payment-requests', {
      amount: "1000000", // $1.00 USDC
      token_id: 1,
      chain_id: 1,
      recipient_address: "0x742C4B8C2515Ad1D8e1C71e8e24c8D5F9A1a8B5A",
      external_id: "order_123",
      description: "Monthly subscription",
      redirect_url: "https://myapp.com/success",
      expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString()
    });

    console.log('Payment request created:', response.data.data.payment_url);
    return response.data.data;
  } catch (error) {
    if (error.response?.status === 422) {
      console.error('Validation errors:', error.response.data.errors);
    } else {
      console.error('Error:', error.response?.data || error.message);
    }
    throw error;
  }
}

Payment Request Lifecycle

  1. Active: Payment request is created and ready to receive payments

  2. Completed: Full amount has been received

  3. Expired: Past the expiration date without full payment

  4. Cancelled: Manually cancelled via API

Best Practices

  1. Always set reasonable expiration times

  2. Use external_id for tracking

    • Link payment requests to your internal order/invoice system

  3. Handle webhook notifications

    • Set up webhooks to get notified of payment status changes

  4. Validate token/chain combinations

    • Use the chains and tokens endpoints to get valid combinations

    • Not all tokens are available as receiving token on all chains

Getting Available Tokens and Chains

Before creating payment requests, you need to know which tokens and chains are supported. Use these public endpoints (no authentication required):

List All Supported Chains

GET /api/public/chains?include_tokens=true

List Tokens for a Specific Chain

GET /api/public/chains/{chain_id}/tokens

Last updated