curl -X POST "${this.baseUrl}/v2/payments" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "te-abc123def456",
    "to": [
      {
        "to": "us-xyz789abc123",
        "amount_cents": 10000,
        "currency_symbol": "USD",
        "invoice_description": "Salary payment for January 2024"
      },
      {
        "to": "us-def456ghi789",
        "amount_cents": 15000, 
        "currency_symbol": "USD",
        "invoice_description": "Bonus payment"
      }
    ],
    "pay_now": true,
    "network": "arbitrum"
  }'
{
  "success": true,
  "data": {
    "domain": {
      "name": "RiseAccountForwarder",
      "version": "1.0.0",
      "chainId": 42161,
      "verifyingContract": "0x..."
    },
    "types": {
      "CreatePaymentsForwardRequest": [...]
    },
    "typed_data": {
      "from": "0x...",
      "to": "0x...",
      "salt": "12345",
      "expires": "1750492712",
      "data": [...]
    },
    "primary_type": "CreatePaymentsForwardRequest"
  }
}
Rise provides a comprehensive payment system that enables batch payments with blockchain security. This guide covers the complete payment flow from creating payments to executing them on-chain.

Payment Flow Overview

1

Create Payment

Submit payment details to create a draft payment group
2

Sign TypedData

Sign the payment transaction using EIP-712 typed data
3

Submit Signature

Submit the signed transaction to execute the payment
4

Monitor Status

Track payment status and confirmations

Payment Types

Batch Payments

Process multiple payments in a single transaction for efficiency and cost savings.

Individual Payments

Process single payments with immediate execution.

Creating Payments

Step 1: Create Payment Draft

First, create a payment draft with the recipient details:
curl -X POST "${this.baseUrl}/v2/payments" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "te-abc123def456",
    "to": [
      {
        "to": "us-xyz789abc123",
        "amount_cents": 10000,
        "currency_symbol": "USD",
        "invoice_description": "Salary payment for January 2024"
      },
      {
        "to": "us-def456ghi789",
        "amount_cents": 15000, 
        "currency_symbol": "USD",
        "invoice_description": "Bonus payment"
      }
    ],
    "pay_now": true,
    "network": "arbitrum"
  }'
{
  "success": true,
  "data": {
    "domain": {
      "name": "RiseAccountForwarder",
      "version": "1.0.0",
      "chainId": 42161,
      "verifyingContract": "0x..."
    },
    "types": {
      "CreatePaymentsForwardRequest": [...]
    },
    "typed_data": {
      "from": "0x...",
      "to": "0x...",
      "salt": "12345",
      "expires": "1750492712",
      "data": [...]
    },
    "primary_type": "CreatePaymentsForwardRequest"
  }
}

Step 2: Sign the TypedData

Sign the typed data using EIP-712 standard:
const { ethers } = require('ethers');

async function signPayment(typedData, privateKey) {
  const wallet = new ethers.Wallet(privateKey);
  
  // Sign the typed data
  const signature = await wallet.signTypedData(
    typedData.domain,
    typedData.types,
    typedData.typed_data
  );
  
  return signature;
}

// Usage
const signature = await signPayment(response.data, process.env.WALLET_PRIVATE_KEY);

Step 3: Submit Signed Payment

Submit the signed payment to execute the transaction:
curl -X PUT "${this.baseUrl}/v2/payments" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "signer": "0x1234567890abcdef...",
    "from": "te-abc123def456",
    "to": [
      {
        "to": "us-xyz789abc123",
        "amount_cents": 10000,
        "currency_symbol": "USD",
        "invoice_description": "Salary payment for January 2024"
      },
      {
        "to": "us-def456ghi789", 
        "amount_cents": 15000,
        "currency_symbol": "USD",
        "invoice_description": "Bonus payment"
      }
    ],
    "pay_now": true,
    "network": "arbitrum",
    "typed_data": {...},
    "signature": "0x..."
  }'
{
  "success": true,
  "data": {
    "transaction": "tx-abc123def456",
    "payments": [
      {
        "id": "pay-xyz789abc123",
        "groupID": "group-123",
        "payAtTime": "1704067200",
        "validMinutes": "1440",
        "payType": "immediate",
        "token": "0x...",
        "recipient": "0x...",
        "amount": "1000000000000000000",
        "data": "0x..."
      }
    ]
  }
}

Complete Payment Integration Example

Here’s a complete example of the payment integration:
const { ethers } = require('ethers');
require('dotenv').config();

class RisePayments {
  constructor(baseUrl, jwtToken) {
    this.baseUrl = baseUrl;
    this.headers = {
      'Authorization': `Bearer ${jwtToken}`,
      'Content-Type': 'application/json'
    };
  }

  async createPayment(
    teamNanoid,
    recipients,
    walletAddress,
    privateKey,
    payNow = true
  ) {
    // Step 1: Create payment draft
    const createResponse = await fetch(`${this.baseUrl}/v2/payments`, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify({
        from: teamNanoid,
        to: recipients,
        pay_now: payNow,
        network: 'arbitrum',
      }),
    });

    if (!createResponse.ok) {
      const errorText = await createResponse.text();
      throw new Error(
        `Failed to create payment draft: ${createResponse.status} - ${errorText}`
      );
    }

    const { data: typedData } = await createResponse.json();
    console.log('✅ Payment draft created:', typedData);

    // Step 2: Sign typed data
    const wallet = new ethers.Wallet(privateKey);
    const signature = await wallet.signTypedData(
      typedData.domain,
      typedData.types,
      typedData.typed_data
    );

    // Step 3: Execute payment
    const executeResponse = await fetch(`${this.baseUrl}/v2/payments`, {
      method: 'PUT',
      headers: this.headers,
      body: JSON.stringify({
        signer: walletAddress,
        from: teamNanoid,
        to: recipients,
        pay_now: payNow,
        typed_data: typedData.typed_data,
        signature: signature,
      }),
    });

    const response = await executeResponse.json();
    console.log('✅ Payment executed:', response.data);
    return response.data;
  }

  async queryPayments(teamNanoid, options = {}) {
    const params = new URLSearchParams({
      team_nanoid: teamNanoid,
      state: options.state || 'all',
      query_type: options.queryType || 'payable',
      ...(options.startDate && { start_date: options.startDate }),
      ...(options.endDate && { end_date: options.endDate }),
      ...(options.recipient && { recipient: options.recipient })
    });
    
    const response = await fetch(`${this.baseUrl}/v2/payments?${params}`, {
      headers: this.headers
    });
    
    return response.json();
  }
}

// Usage example
const payments = new RisePayments('your-base-url', jwtToken); // Configure for your environment

const to = [
  {
    to: 'us-jRxg2LRL54DJ',
    amount_cents: 300,
    currency_symbol: 'USD',
    invoice_description: 'Papa Sent you',
  },
  {
    to: 'us-d6JHBF2kuZjE',
    amount_cents: 700,
    currency_symbol: 'USD',
    invoice_description: 'Papa Sent you',
  },
];

const payment = await payments.createPayment(
  'te-bXy7gjb_Iga-',
  to,
  process.env.WALLET_ADDRESS,
  process.env.WALLET_PRIVATE_KEY,
  true // true = pay immediately, false = pay intent (pay later)
);

React Hook for Payments

import { useState } from 'react';
import { ethers } from 'ethers';

interface Recipient {
  to: string;
  amount_cents: number;
  currency_symbol: string;
  invoice_description?: string;
}

interface UseRisePayments {
  processing: boolean;
  error: string | null;
  createPayment: (
    teamNanoid: string, 
    recipients: Recipient[], 
    walletAddress: string,
    privateKey: string,
    payNow?: boolean
  ) => Promise<any>;
}

export function useRisePayments(jwtToken: string): UseRisePayments {
  const [processing, setProcessing] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const createPayment = async (
    teamNanoid: string, 
    recipients: Recipient[], 
    walletAddress: string,
    privateKey: string,
    payNow: boolean = true
  ) => {
    setProcessing(true);
    setError(null);
    
    try {
      const payments = new RisePayments('your-base-url', jwtToken); // Configure for your environment
      const result = await payments.createPayment(
        teamNanoid,
        recipients,
        walletAddress,
        privateKey,
        payNow
      );
      return result;
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Payment failed';
      setError(errorMessage);
      throw err;
    } finally {
      setProcessing(false);
    }
  };

  return { processing, error, createPayment };
}

Mobile Integration (React Native)

import { ethers } from 'ethers';
import { WalletConnectModal } from '@walletconnect/modal-react-native';

// For React Native, use WalletConnect or similar
const signPaymentMobile = async (typedData: any) => {
  const provider = new ethers.JsonRpcProvider('https://arb1.arbitrum.io/rpc');
  
  // Connect via WalletConnect
  const connector = new WalletConnectModal({
    projectId: 'your-project-id',
    chains: [42161], // Arbitrum
  });
  
  await connector.connect();
  const signer = await provider.getSigner();
  
  return await signer.signTypedData(
    typedData.domain,
    typedData.types,
    typedData.typed_data
  );
};

Querying Payment History

Retrieve payment history with filtering options:
curl -X GET "${this.baseUrl}/v2/payments?team_nanoid=te-abc123def456&state=all&start_date=2024-01-01&end_date=2024-01-31&query_type=payable" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"
{
  "success": true,
  "data": {
    "items": [
      {
        "recipient": {
          "nanoid": "us-xyz789abc123",
          "riseid": "rise_123",
          "avatar": "https://...",
          "first_name": "John",
          "last_name": "Doe",
          "email": "john@example.com"
        },
        "payer": {
          "nanoid": "te-abc123def456",
          "riseid": "rise_456",
          "avatar": "https://...",
          "name": "Team Alpha"
        },
        "payment": {
          "state": "Complete",
          "amount_cents": 10000,
          "currency_symbol": "USD",
          "created_at": "2024-01-15T10:30:00Z"
        }
      }
    ]
  }
}

Payment Status Tracking

Monitor payment status through the transaction hash:
class PaymentTracker {
  constructor(baseUrl, jwtToken) {
    this.baseUrl = baseUrl;
    this.headers = {
      'Authorization': `Bearer ${jwtToken}`,
      'Content-Type': 'application/json'
    };
  }

  async queryPayments(teamNanoid, options = {}) {
    const params = new URLSearchParams({
      team_nanoid: teamNanoid,
      state: options.state || 'all',
      query_type: options.queryType || 'payable',
      ...(options.startDate && { start_date: options.startDate }),
      ...(options.endDate && { end_date: options.endDate }),
      ...(options.recipient && { recipient: options.recipient })
    });
    
    const response = await fetch(`${this.baseUrl}/v2/payments?${params}`, {
      headers: this.headers
    });
    return response.json();
  }

  async waitForConfirmation(transactionHash, maxAttempts = 30) {
    const provider = new ethers.JsonRpcProvider('https://arb1.arbitrum.io/rpc');
    
    for (let i = 0; i < maxAttempts; i++) {
      const receipt = await provider.getTransactionReceipt(transactionHash);
      
      if (receipt && receipt.confirmations > 0) {
        return {
          confirmed: true,
          blockNumber: receipt.blockNumber,
          gasUsed: receipt.gasUsed.toString(),
          status: receipt.status === 1 ? 'success' : 'failed'
        };
      }
      
      // Wait 10 seconds before next check
      await new Promise(resolve => setTimeout(resolve, 10000));
    }
    
    throw new Error('Transaction confirmation timeout');
  }
}

Payment Timing Options

Rise supports two payment timing options:
OptionDescriptionUse Case
pay_now: truePayment executes immediatelyInstant payroll, urgent payments
pay_now: falsePayment intent (scheduled)Future payroll, scheduled payments

Error Handling

Common payment errors and solutions:
Error CodeDescriptionSolution
INSUFFICIENT_BALANCENot enough funds in the source accountCheck entity balance before creating payments
INVALID_SIGNATURETypedData signature verification failedEnsure correct signing of the typed data
EXPIRED_DEADLINEPayment deadline has passedCreate a new payment with updated deadline
INVALID_RECIPIENTRecipient address is not validVerify recipient addresses and permissions
PAYMENT_LIMIT_EXCEEDEDPayment amount exceeds limitsCheck payment limits for the entity

Security Best Practices

Always verify payment details before signing. Double-check amounts, recipients, and payment descriptions.
  • Signature Verification: Always verify typed data signatures on-chain
  • Deadline Management: Set appropriate deadlines for payment execution
  • Amount Validation: Validate payment amounts before submission
  • Recipient Verification: Ensure recipients have valid Rise accounts
  • Gas Estimation: Estimate gas costs before executing payments
  • Wallet Security: Keep private keys secure and never expose them in client-side code
Need help? See the Payments page for detailed payment concepts or contact support at Hello@Riseworks.io

Testing Payments

For testing, use the development environment:
// Development configuration
const DEV_CONFIG = {
  baseUrl: 'https://dev-pay-api.riseworks.dev',
  network: 'arbitrum',
  chainId: 42161
};

// Test payment with small amounts
const testRecipients = [
  {
    to: 'us-test-user-nanoid',
    amount_cents: 100,
    currency_symbol: 'USD',
    invoice_description: 'Test payment'
  }
];

Real-World Integration Example

Payroll System Integration

interface PayrollRun {
  id: string;
  employees: Array<{
    user_nanoid: string;
    amount_cents: number;
    description: string;
  }>;
  team_nanoid: string;
  pay_date: string;
}

class PayrollSystem {
  private payments: RisePayments;

  constructor(jwtToken: string) {
    this.payments = new RisePayments('https://b2b-api.riseworks.io', jwtToken);
  }

  async processPayrollRun(payrollRun: PayrollRun, walletAddress: string, privateKey: string) {
    const recipients = payrollRun.employees.map(emp => ({
      to: emp.user_nanoid,
      amount_cents: emp.amount_cents,
      currency_symbol: 'USD',
      invoice_description: `${emp.description} - ${payrollRun.pay_date}`
    }));

    try {
      const result = await this.payments.createPayment(
        payrollRun.team_nanoid,
        recipients,
        walletAddress,
        privateKey,
        true // Pay immediately
      );

      // Store payroll run result
      await this.storePayrollResult(payrollRun.id, result);

      return result;
    } catch (error) {
      console.error(`Payroll run ${payrollRun.id} failed:`, error);
      throw error;
    }
  }

  private async storePayrollResult(payrollId: string, result: any) {
    // Store in your database
    console.log(`Payroll ${payrollId} processed:`, result.transaction);
  }
}