Overview
Payroll APIs enable you to distribute cryptocurrency payments to multiple recipients efficiently. This feature is designed for bulk salary payments, contractor disbursements, and recurring employee compensation.
When to Use Payroll vs Payout
| Feature | Payroll | Payout |
|---|---|---|
| Best For | Bulk salary payments, employee disbursements | Personal withdrawals, large transfers |
| Batch Processing | ✅ Multiple recipients in one operation | ❌ Single recipient per payout |
| Address Verification | ⚠️ Optional (higher risk for new addresses) | ✅ Required (micro-payment confirmation) |
| Typical Use Case | Paying multiple employees | Withdrawing your own funds |
| Recommended Amount | Under $10,000 USD per recipient | Any amount, especially large transfers |
| Processing Methods | CSV Upload or Direct API | Always via API |
Key Decision Point:
- Use Payroll when paying multiple employees/contractors simultaneously (especially under $10,000 USD each)
- Use Payout when withdrawing to your own wallets or for any single transfer exceeding $10,000 USD
⚠️ Critical: Crypto Transactions are Irreversible Once a transaction is confirmed on the blockchain, it cannot be reversed or canceled. For individual payments over $10,000 USD, use Payout APIs with pre-verified addresses to prevent costly address errors.
Why Payout for Large Amounts?
The Payout system requires mandatory address verification:
| Step | Payout | Payroll |
|---|---|---|
| Address Verification | ✅ Mandatory micro-payment confirmation | ⚠️ Optional (employee input) |
| Error Prevention | 🔒 Pre-verified before any transfer | ⚠️ Relies on accurate address entry |
| Best For | > $10,000 USD transfers | < $10,000 USD per recipient |
| Setup | One-time verification, reusable | Per-batch address input |
Verification Process (Payout only):
- Register wallet address via Wallet Management APIs
- Receive micro-payment (e.g., 0.003621 USDC)
- Confirm exact amount received
- Address permanently verified for unlimited withdrawals
For detailed information, see How to Manage Wallet and Payout APIs.
How Payroll Works
Processing Methods
MartianPay offers two methods for processing payrolls:
Method 1: Traditional Flow (CSV Upload)
Best For: Dashboard/web operations, manual batch salary processing
Process:
- Create Payroll (upload CSV)
- Confirm Payroll
- Merchant Approval (always required)
- Admin Approval (if total > $2,000)
- Automatic execution
Approval Requirements:
| Total Amount | Merchant Approval | Admin Approval |
|---|---|---|
| ≤ $2,000 | ✅ Required | ❌ Not Required |
| > $2,000 | ✅ Required | ✅ Required |
Method 2: Payroll Direct API ⭐ RECOMMENDED
Best For: API integrations, automated systems, programmatic payroll
Process: Single API call combines create, confirm, and optionally auto-approve
Approval Requirements:
| Single Item Amount | Merchant Approval | Admin Approval | Execution |
|---|---|---|---|
| ≤ $2,000 | ❌ Not Required | ❌ Not Required | ✅ Immediate |
| > $2,000 | ❌ Not Required | ✅ Required | After admin approval |
Key Advantages:
- ✅ No merchant approval needed (unlike CSV)
- ✅ Single API call execution
- ✅ Automatic approval for amounts ≤ $2,000
- ✅ Ideal for automated payment systems
Important Notes:
- CSV Upload: All payrolls created via web CSV upload require merchant approval
- CSV Upload (total > $2,000): Also requires admin approval after merchant approval
- Payroll Direct API: No merchant approval required, admin approval only for single items > $2,000
- CSV threshold is based on total amount, Direct API threshold is based on single item amount
- Approval amounts are calculated using USD equivalent at creation time
- During approval, payroll status is
pendingwithapproval_status: "in_progress"
💡 Tip: For API integrations, use Payroll Direct API to skip merchant approval and streamline payment processing.
Understanding Payroll Status
Payrolls track progress through two status fields:
1. Payroll Status (status)
Main processing state of the payroll batch:
| Status | Meaning |
|---|---|
draft | Initial state after creation, awaiting confirmation |
pending | Confirmed and awaiting approval through the approval workflow |
approved | Approved and ready for execution |
started | Processing has begun (transitional state) |
swapping | Currently performing currency swaps between assets |
executing | Processing normal (Fireblocks) payments |
binance_processing | Processing Binance payments |
paid | All payments completed successfully |
partial_paid | Some payments succeeded, some failed |
failed | All payments failed |
rejected | Rejected through the approval workflow, no further processing |
canceled | Payroll was canceled |
2. Approval Status (approval_status)
Approval workflow state:
| Status | Meaning |
|---|---|
"" (empty) | No approval needed yet |
in_progress | Awaiting approval decision |
approved | Approval granted |
rejected | Approval denied |
3. Payroll Item Status
Each individual payment has its own status:
| Status | Meaning |
|---|---|
draft | Initial state |
pending | Awaiting processing |
address_verifying | Checking recipient address |
address_verified | Address confirmed |
executing | Payment in progress |
sent | Transaction on blockchain |
paid | Completed successfully |
failed | Payment failed |
Typical Status Flows
CSV Upload (always requires merchant approval):
draft → pending (merchant approval) → [pending (admin approval if > $2K)]
→ approved → executing → paid/partial_paid/failed
Direct API (≤ $2,000 per item):
approved (instant) → executing → paid/partial_paid/failed
Direct API (> $2,000 per item):
pending (admin approval only) → approved → executing → paid/partial_paid/failed
API Reference
2. Traditional Flow (CSV Upload)
2.1 Create Payroll
Create a new payroll record for an employee.
For detailed API reference, see: Create Payroll API
First, create a CSV file with the following format:
name,email,phone,amount,coin,network,address,payment_method
John Doe,employee1@example.com,13488888888,30,USDC,Solana Devnet,CNJQ5q3kczhLWYQnh3YkKSwnS1fGJ9ydxvGN3KZuqy3G,normal
Jane Smith,employee2@example.com,13488888888,5,USDC,Ethereum Sepolia,0x36279Ac046498bF0cb742622cCe22F3cE3c2AfD9,binance
Request:
# Basic CSV upload
curl --location --request POST 'https://api.martianpay.com/v1/payrolls' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--form 'file=@/path/to/your/payroll.csv'
# With external_id for idempotency
curl --location --request POST 'https://api.martianpay.com/v1/payrolls' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--form 'file=@/path/to/your/payroll.csv' \
--form 'external_id=ORDER-2024-001'
Form Parameters:
file: (Required) CSV file containing payroll dataexternal_id: (Optional) External ID for idempotency. Prevents duplicate payroll creation
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"payroll": {
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750147470,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"external_id": "ORDER-2024-001",
"approval_status": "",
"status": "draft",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
"items": [
{
"id": "payroll_item_QZmpUMN5R1Gxohg7ADKbsnTW",
"created_at": 1750147470,
"updated_at": 1750147470,
"payroll_id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"payroll": {
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750147470,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "",
"status": "draft",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
"name": "John Doe",
"email": "employee1@example.com",
"phone": "13488888888",
"amount": "30",
"service_fee": "2",
"exchange_rate_to_usd": "1",
"coin": "USDC",
"network": "Solana Devnet",
"asset_id": "USDC-Solana-TEST",
"address": "CNJQ5q3kczhLWYQnh3YkKSwnS1fGJ9ydxvGN3KZuqy3G",
"address_verified": false,
"status": "draft",
"transactions": null,
"validation": {
"name_valid": true,
"email_valid": true,
"phone_valid": true,
"amount_valid": true,
"coin_valid": true,
"network_valid": true,
"address_valid": true,
"payment_method_valid": true,
"balance_enough": true
},
"differences": {
"name": {
"previous": "",
"current": "John Doe",
"status": "added"
},
"email": {
"previous": "",
"current": "employee1@example.com",
"status": "added"
},
"phone": {
"previous": "",
"current": "13488888888",
"status": "added"
},
"amount": {
"previous": "",
"current": "30",
"status": "added"
},
"coin": {
"previous": "",
"current": "USDC",
"status": "added"
},
"network": {
"previous": "",
"current": "Solana Devnet",
"status": "added"
},
"address": {
"previous": "",
"current": "CNJQ5q3kczhLWYQnh3YkKSwnS1fGJ9ydxvGN3KZuqy3G",
"status": "added"
}
},
"has_monthly_payment": false
},
{
"id": "payroll_item_nYuCYjVs1OvbOfldKD8A47tV",
"created_at": 1750147470,
"updated_at": 1750147470,
"payroll_id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"payroll": {
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750147470,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "",
"status": "draft",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
"name": "Jane Smith",
"email": "employee1@example.com",
"phone": "13488888888",
"amount": "5",
"service_fee": "2",
"exchange_rate_to_usd": "1",
"coin": "USDC",
"network": "Ethereum Sepolia",
"asset_id": "USDC-Ethereum-TEST",
"address": "0x36279Ac046498bF0cb742622cCe22F3cE3c2AfD9",
"address_verified": true,
"status": "draft",
"transactions": null,
"validation": {
"name_valid": true,
"email_valid": true,
"phone_valid": true,
"amount_valid": true,
"coin_valid": true,
"network_valid": true,
"address_valid": true,
"payment_method_valid": true,
"balance_enough": true
},
"differences": {
"name": {
"previous": "Jane Smith",
"current": "Jane Smith",
"status": "unchanged"
},
"email": {
"previous": "employee2@example.com",
"current": "employee1@example.com",
"status": "unchanged"
},
"phone": {
"previous": "134333333333",
"current": "13488888888",
"status": "modified"
},
"amount": {
"previous": "5",
"current": "5",
"status": "unchanged"
},
"coin": {
"previous": "USDC",
"current": "USDC",
"status": "unchanged"
},
"network": {
"previous": "Ethereum Sepolia",
"current": "Ethereum Sepolia",
"status": "unchanged"
},
"address": {
"previous": "0x36279Ac046498bF0cb742622cCe22F3cE3c2AfD9",
"current": "0x36279Ac046498bF0cb742622cCe22F3cE3c2AfD9",
"status": "unchanged"
}
},
"has_monthly_payment": true
}
],
"swap_items": [
{
"id": "payroll_swap_item_n4rBJt7yuh6fVYCunWxQiJrd",
"lifi_quote_id": "f7800f87-ad2f-4411-b4e5-9a38eb03410e:0",
"created_at": 1750147470,
"updated_at": 1750147470,
"sent_at": null,
"merchant_id": "",
"payroll_id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"payroll": {
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750147470,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "",
"status": "draft",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
"from_asset_id": "USDC-Ethereum-TEST",
"expected_from_amount": "0",
"estimated_from_amount": "32.776548",
"estimated_from_amount_usd": "32.7715",
"actual_from_amount": "0",
"network_fee": "0.03",
"to_asset_id": "USDC-Solana-TEST",
"expected_to_amount": "32",
"estimated_to_amount": "32.173331",
"estimated_to_amount_min": "32.012464",
"estimated_to_amount_usd": "32.1638",
"actual_to_amount": "0",
"status": "draft"
}
]
}
}
Understanding the Response
How to Check if the Request Was Successful:
| Field | Value | Meaning |
|---|---|---|
error_code | "success" | ✅ Payroll created |
error_code | Other value | ❌ Error occurred (e.g., "duplicate_external_id", "invalid_csv_format") |
Key Status Fields:
| Field | Value | Meaning |
|---|---|---|
status | "draft" | 📝 Payroll created, awaiting confirmation |
status | "pending" | ⏳ Confirmed, awaiting approval |
status | "approved" | ✅ Approved, ready for execution |
status | "executing" | 🔄 Processing payments |
status | "paid" | ✅ Success - All payments completed |
status | "partial_paid" | ⚠️ Some payments succeeded, some failed |
status | "failed" | ❌ Failed - All payments failed |
status | "canceled" | ❌ Manually canceled |
approval_status | "" (empty) | No approval needed yet |
approval_status | "in_progress" | ⏳ Awaiting merchant/admin approval |
approval_status | "approved" | ✅ All required approvals granted |
approval_status | "rejected" | ❌ Approval denied |
Payroll Item Status:
Each item in the items array has its own status field:
| Status | Meaning | Action Required |
|---|---|---|
draft | Item created | Confirm payroll to proceed |
pending | Awaiting processing | Wait for execution |
address_verifying | Checking recipient address | Automatic process |
address_verified | Address confirmed | Ready for payment |
executing | Payment in progress | Monitor for completion |
paid | ✅ Payment succeeded | No action needed |
failed | ❌ Payment failed | Check failure reason |
Validation Fields:
The validation object contains fields to verify the validity of payroll item data:
| Field | Description |
|---|---|
name_valid | Indicates if the recipient's name is valid |
email_valid | Indicates if the email address format is valid |
phone_valid | Indicates if the phone number format is valid |
amount_valid | Indicates if the payment amount is valid |
coin_valid | Indicates if the cryptocurrency is supported |
network_valid | Indicates if the blockchain network is supported |
address_valid | Indicates if the wallet address format is valid |
payment_method_valid | Indicates if the payment method is valid (must be "normal" or "binance") |
balance_enough | Indicates if your account has sufficient balance |
Important: Any field with a false value requires immediate attention. These indicate validation failures that must be corrected before the payroll can be processed successfully.
Using External ID:
- If you provided an
external_idduring creation, you can use it to query the payroll:GET /payrolls?external_id=ORDER-2024-001- Find the specific payrollGET /payrolls/items/list?external_id=ORDER-2024-001- List items for that payroll
- The external_id ensures idempotency - attempting to create another payroll with the same external_id will return an error
2.2 Confirm Payroll
Confirm the payroll to initiate the payment process.
For detailed API reference, see: Confirm Payroll API
Before confirming a payroll, all validation checks must pass. If any of the following validation fields returns false, the confirmation will fail:
name_validemail_validphone_validamount_validcoin_validnetwork_validaddress_validpayment_method_validbalance_enough
Ensure all validation issues are resolved before attempting to confirm the payroll. The system will not process payrolls with invalid data.
Request:
curl --location --request POST 'https://api.martianpay.com/v1/payrolls/payroll_I6mzarIW0qZ4itwGg7xCa9EA/confirm' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"payroll": {
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750147834,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "in_progress",
"status": "pending",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
"items": [
// ... payroll items
],
"swap_items": [
// ... swap items if any
]
}
}
Understanding the Response
How to Check if the Request Was Successful:
| Field | Value | Meaning |
|---|---|---|
error_code | "success" | ✅ Confirmation accepted |
error_code | Other value | ❌ Error occurred (e.g., "validation_failed", "already_confirmed") |
Status After Confirmation:
| Field | Value | Meaning |
|---|---|---|
status | "pending" | ✅ Confirmed, now awaiting approval |
approval_status | "in_progress" | ⏳ Merchant approval required |
Next Steps:
After confirming, the payroll enters pending status with approval_status: "in_progress". You'll need to:
- Approve via Dashboard: Log in to MartianPay dashboard to review and approve
- Approve via API: Use Approve Payroll API (see Section 4.2)
- Monitor Status: Poll the payroll status or subscribe to webhook events
CSV Upload Workflow:
draft → (confirm) → pending + in_progress → (approve) → approved → executing → paid
3. Direct API (Simplified) ⭐ RECOMMENDED for API Integration
💡 Important for API Integration: Direct API is the RECOMMENDED method for API integrations. This endpoint can directly execute payroll payments in a single API call, combining create, confirm, and approve steps into one streamlined process.
Use Direct API for: API integrations, automated systems, programmatic payroll processing Use CSV Upload/Approve Workflow for: Dashboard/web manual operations, batch employee salary payments requiring human review
Create and optionally auto-approve a payroll in a single API call. This endpoint combines the create, confirm, and approve steps into one streamlined process.
For detailed API reference, see: Create Payroll Direct API
Key Features:
- Idempotency: Use
external_idto prevent duplicate payroll creation - Auto-approval: Optional automatic approval and execution
- Smart defaults: Automatically fills missing employee information
Request:
curl --location --request POST 'https://api.martianpay.com/v1/payrolls/direct' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data-raw '{
"external_id": "ORDER-2024-001",
"auto_approve": true,
"items": [
{
"coin": "USDT",
"network": "Tron",
"address": "TN9RRaXkCFtTXRso2GdTZxSxxwufzxLQPP",
"amount": "100",
"payment_method": "normal"
},
{
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"coin": "USDT",
"network": "Tron",
"address": "TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb",
"amount": "200",
"payment_method": "binance"
}
]
}'
Request Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
external_id | string | No | External ID for idempotency. Prevents duplicate payroll creation |
auto_approve | boolean | No | If true, automatically approves and executes the payroll. Default is false |
items | array | Yes | Array of payroll items to process |
items[].name | string | No | Employee name. If empty, generates "Recipient-" followed by last 6 characters of address |
items[].email | string | No | Employee email. Defaults to "-" if not provided |
items[].phone | string | No | Employee phone. Defaults to "-" if not provided |
items[].coin | string | Yes | Cryptocurrency type (e.g., USDT, USDC) |
items[].network | string | Yes | Blockchain network (e.g., Tron, Ethereum, Solana) |
items[].address | string | Yes | Wallet address to send funds to |
items[].amount | string | Yes | Amount to send |
items[].payment_method | string | No | Payment method: "normal" or "binance". Defaults to "normal" |
Payment Method Explanation:
The payment_method field is used to specify the payment method for each employee's payroll. Common values include:
normal: Indicates a standard on-chain transfer, where the salary is sent directly to the employee's provided wallet address.binance: Indicates payment via the Binance platform, suitable when the employee's wallet address is associated with Binance.
Choose the appropriate payment_method according to your needs to ensure employees receive their salaries smoothly.
Key Features:
- Single API Call: Combines create, confirm, and optionally approve in one request
- Smart Defaults: Automatically generates employee names from wallet addresses when not provided
- Optional Fields: Name, email, and phone are optional, making it ideal for automated payroll processing
- Auto-Approval: Set
auto_approve: trueto bypass the approval workflow and execute immediately - Bulk Processing: Send payments to multiple recipients in a single request
- Mixed Payment Methods: Support both "normal" (Fireblocks) and "binance" payment methods in the same payroll
- Idempotency Support: Use
external_idto prevent duplicate payroll creation
Using External ID for Idempotency:
- The
external_idfield ensures that duplicate requests with the same ID won't create multiple payrolls - Once a payroll is created with an
external_id, subsequent requests with the same ID will return an error - You can query payrolls and payroll items using the
external_id:GET /payrolls?external_id=ORDER-2024-001- Find specific payrollGET /payrolls/items/list?external_id=ORDER-2024-001- List items for that payroll
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"payroll": {
"id": "payroll_xxxxx",
"created_at": 1750147470,
"updated_at": 1750147470,
"merchant_id": "accu_xxxxx",
"external_id": "ORDER-2024-001",
"status": "approved", // or "pending" if auto_approve is false
"approval_status": "approved", // or "in_progress" if auto_approve is false
"total_item_num": 2,
"total_amount": "300",
"total_service_fee": "4",
"currency": "USD"
},
"items": [
{
"id": "payroll_item_xxxxx",
"name": "Recipient-LQQPP", // Auto-generated from address if name not provided
"email": "-",
"phone": "-",
"amount": "100",
"coin": "USDT",
"network": "Tron",
"address": "TN9RRaXkCFtTXRso2GdTZxSxxwufzxLQPP",
"payment_method": "normal",
"status": "approved",
"validation": {
"name_valid": true,
"email_valid": true,
"phone_valid": true,
"amount_valid": true,
"coin_valid": true,
"network_valid": true,
"address_valid": true,
"balance_enough": true
}
},
{
"id": "payroll_item_yyyyy",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"amount": "200",
"coin": "USDT",
"network": "Tron",
"address": "TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb",
"payment_method": "binance",
"status": "approved"
// ... validation and other fields
}
],
"swap_items": [],
"binance_from_items": []
}
}
Understanding the Response
How to Check if the Request Was Successful:
| Field | Value | Meaning |
|---|---|---|
error_code | "success" | ✅ Payroll created and processed |
error_code | Other value | ❌ Error occurred (e.g., "duplicate_external_id", "insufficient_balance") |
Key Status Fields (Depends on auto_approve):
When auto_approve: true (Recommended for API):
| Field | Value | Meaning |
|---|---|---|
status | "approved" | ✅ Auto-approved, executing immediately |
approval_status | "approved" | ✅ No manual approval needed |
When auto_approve: false (Default):
| Field | Value | Meaning |
|---|---|---|
status | "pending" | ⏳ Awaiting approval |
approval_status | "in_progress" | ⏳ Admin approval required (if total > $2,000) |
Approval Behavior:
| Single Item Amount | auto_approve: true | auto_approve: false | Next Steps |
|---|---|---|---|
| ≤ $2,000 | ✅ Executes immediately | ⚠️ No approvals needed, executes automatically | Monitor for completion |
| > $2,000 | ⚠️ Waits for admin approval | ⚠️ Waits for admin approval | Approve via dashboard or API |
Monitoring Payroll Progress:
After creating a Direct API payroll, monitor through:
-
Polling: Use Get Payroll API to check
statusandapproval_status -
Webhooks: Subscribe to payroll and payroll item events (recommended):
Payroll Batch Events:
payroll.created- Payroll batch createdpayroll.approved- Payroll batch approvedpayroll.rejected- Payroll batch rejectedpayroll.canceled- Payroll batch canceledpayroll.executing- Payroll batch executingpayroll.completed- All payments completed successfullypayroll.failed- All payments failed
Payroll Item Events (recommended for detailed tracking):
payroll_item.processing- Individual payment processingpayroll_item.succeeded- Individual payment succeededpayroll_item.failed- Individual payment failedpayroll_item.address_verification_sent- Verification sentpayroll_item.address_verified- Address verified
💡 Recommendation: Monitor
payroll_item.*events for granular per-employee payment tracking. See Section 7: Webhook Events for detailed event documentation.
Next Steps Based on Status:
approved: ✅ System processing automatically, monitor forpaidstatuspending+approval_status: "in_progress": Approve via Approve Payroll APIexecuting: 🔄 Payments in progress, wait for completionpaid: ✅ Complete! All payments succeededpartial_paid: ⚠️ Check individual item statuses for failuresfailed: ❌ Check failure reasons, resolve and retry
Use Cases:
- Automated Payroll Systems: Ideal for systems that need to process payroll without manual intervention
- Bulk USDT Payments: Efficiently handle multiple USDT transfers on Tron network
- Mixed Payment Processing: Use different payment processors (Fireblocks and Binance) for different recipients
- Quick Disbursements: Skip the approval process for urgent payments when
auto_approveis enabled
Direct API Workflow:
With auto_approve: true (≤ $2,000 per item):
approved (instant) → executing → paid/partial_paid/failed
With auto_approve: false or > $2,000 per item:
pending + in_progress → (admin approval) → approved → executing → paid/partial_paid/failed
4. Approval Workflow
Both Traditional Flow and Direct API (when auto_approve is false) use the same approval workflow.
4.1 Get Payroll Approval Instance
Get the approval instance for a specific payroll.
For detailed API reference, see: Get Approval Instance API
Request:
curl --location --request GET 'https://api.martianpay.com/v1/approval/detail?resource_id=payroll_I6mzarIW0qZ4itwGg7xCa9EA' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"id": "approval_instance_tkACb5ZRUHkBM00JhQsPpuzz",
"object": "approval_instance",
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"status": "pending",
"resource_id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"resource_type": "payroll",
"created_at": 1750147834,
"updated_at": 1750147834,
"records": [
{
"id": "approval_record_G7BWMc5FTnWUIg3sBC1poG6Y",
"action": "start",
"comment": "",
"approver_id": "server-HwTzmQGe2oP9pizy21NsuBEe",
"approver_name": "Jane Smith",
"approver_role": "",
"created_at": 1750147834,
"namespace": "merchant"
}
],
"current_step": {
"id": "approval_step_uKL6OR1NHi5uyfTNPK1i7n3q",
"flow_id": "",
"step_order": 1,
"namespace": "merchant",
"roles": [
"FinanceManager",
"SuperAdmin"
],
"next_on_approve": 2,
"next_on_reject": 0,
"created_at": 1749000373,
"updated_at": 1749000373
}
}
}
4.2 Approve Payroll
Approve a specific payroll request.
For detailed API reference, see: Approve Payroll API
Request:
curl --location --request POST 'https://api.martianpay.com/v1/approval/approval_instance_tkACb5ZRUHkBM00JhQsPpuzz/approve' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data-raw '{
"comment": "agree"
}'
Response:
{
"error_code": "success",
"msg": "success"
}
4.3 Reject Payroll
Reject a specific payroll request.
For detailed API reference, see: Reject Payroll API
Request:
curl --location --request POST 'https://api.martianpay.com/v1/approval/approval_instance_tkACb5ZRUHkBM00JhQsPpuzz/reject' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data-raw '{
"comment": "disagree"
}'
Response:
{
"error_code": "success",
"msg": "success"
}
5. Payroll Execution Process
Once a payroll is approved (either through traditional approval workflow or auto-approval in Direct API), the system automatically executes the payments. The execution process differs based on the payment method:
Address Verification Process
For normal payment method:
- First-time addresses: A micro-transfer is sent for verification
- Verification email: Employee receives an email with a verification link
- 72-hour deadline: Employee must verify within 72 hours or the transaction fails
- Future payments: Once verified, the address doesn't need re-verification
For binance payment method:
- No address verification: Payments are processed directly through Binance
- Immediate processing: No micro-transfer or verification email required
- Suitable for: Binance-associated wallet addresses
Payment Confirmation
After successful execution (regardless of payment method):
- Confirmation email: Employees receive payment details via email
- Transaction details: Amount, cryptocurrency, network, and transaction hash
- Blockchain tracking: Transaction can be verified on blockchain explorer
This dual-method approach ensures:
- Security: Address verification for standard on-chain transfers
- Efficiency: Streamlined processing for Binance payments
- Flexibility: Choose the appropriate method based on recipient needs
6. Common Operations
6.1 List Payrolls
List all payrolls for a merchant.
For detailed API reference, see: List Payrolls API
Request:
curl --location --request GET 'https://api.martianpay.com/v1/payrolls?page=0&page_size=5' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'
Query Parameters:
page(required): Page number, starting from 0page_size(required): Items per page (1-50)start_date(optional): Filter by start date (YYYY-MM-DD)end_date(optional): Filter by end date (YYYY-MM-DD)external_id(optional): Filter by external ID to find specific payrollpayroll_id(optional): Filter by payroll IDstatus(optional): Filter by status (draft, pending, approved, paid, etc.)
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"payrolls": [
{
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750147834,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "in_progress",
"status": "pending",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
{
"id": "payroll_dHQES72TDZZBamNx0gpMOeko",
"created_at": 1750143877,
"updated_at": 1750158632,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "rejected",
"status": "rejected",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
{
"id": "payroll_3CB4JUyTOSWZec5jPa4Y08ok",
"created_at": 1749955685,
"updated_at": 1749956301,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "approved",
"status": "paid",
"total_item_num": 2,
"total_amount": "10",
"total_service_fee": "4",
"currency": "USD"
},
{
"id": "payroll_FDsfD98wyjVP3YwhCUApiXvu",
"created_at": 1749955619,
"updated_at": 1749955663,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "approved",
"status": "failed",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
{
"id": "payroll_7gVHYolRb2T36CAYCJC9nGsb",
"created_at": 1749954848,
"updated_at": 1749954900,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "approved",
"status": "failed",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
}
],
"total": 38
}
}
6.2 Get Payroll Detail
Get the details of a specific payroll request.
For detailed API reference, see: Get Payroll API
Request:
curl --location --request GET 'https://api.martianpay.com/v1/payrolls/payroll_I6mzarIW0qZ4itwGg7xCa9EA' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"payroll": {
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750159306,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "rejected",
"status": "rejected",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
"items": [
{
"id": "payroll_item_QZmpUMN5R1Gxohg7ADKbsnTW",
"created_at": 1750147470,
"updated_at": 1750159306,
"payroll_id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"payroll": {
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750159306,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "rejected",
"status": "rejected",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
"name": "John Doe",
"email": "employee1@example.com",
"phone": "13488888888",
"amount": "30",
"service_fee": "2",
"exchange_rate_to_usd": "1",
"coin": "USDC",
"network": "Solana Devnet",
"asset_id": "USDC-Solana-TEST",
"address": "CNJQ5q3kczhLWYQnh3YkKSwnS1fGJ9ydxvGN3KZuqy3G",
"address_verified": false,
"status": "rejected",
"transactions": null,
"validation": {
"name_valid": true,
"email_valid": true,
"phone_valid": true,
"amount_valid": true,
"coin_valid": true,
"network_valid": true,
"address_valid": true,
"payment_method_valid": true,
"balance_enough": true
},
"differences": {
"name": {
"previous": "",
"current": "John Doe",
"status": "added"
},
"email": {
"previous": "",
"current": "employee1@example.com",
"status": "added"
},
"phone": {
"previous": "",
"current": "13488888888",
"status": "added"
},
"amount": {
"previous": "",
"current": "30",
"status": "added"
},
"coin": {
"previous": "",
"current": "USDC",
"status": "added"
},
"network": {
"previous": "",
"current": "Solana Devnet",
"status": "added"
},
"address": {
"previous": "",
"current": "CNJQ5q3kczhLWYQnh3YkKSwnS1fGJ9ydxvGN3KZuqy3G",
"status": "added"
}
},
"has_monthly_payment": false
},
{
"id": "payroll_item_nYuCYjVs1OvbOfldKD8A47tV",
"created_at": 1750147470,
"updated_at": 1750159306,
"payroll_id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"payroll": {
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750159306,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "rejected",
"status": "rejected",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
"name": "Jane Smith",
"email": "employee1@example.com",
"phone": "13488888888",
"amount": "5",
"service_fee": "2",
"exchange_rate_to_usd": "1",
"coin": "USDC",
"network": "Ethereum Sepolia",
"asset_id": "USDC-Ethereum-TEST",
"address": "0x36279Ac046498bF0cb742622cCe22F3cE3c2AfD9",
"address_verified": true,
"status": "rejected",
"transactions": null,
"validation": {
"name_valid": true,
"email_valid": true,
"phone_valid": true,
"amount_valid": true,
"coin_valid": true,
"network_valid": true,
"address_valid": true,
"payment_method_valid": true,
"balance_enough": true
},
"differences": {
"name": {
"previous": "Jane Smith",
"current": "Jane Smith",
"status": "unchanged"
},
"email": {
"previous": "employee2@example.com",
"current": "employee1@example.com",
"status": "unchanged"
},
"phone": {
"previous": "134333333333",
"current": "13488888888",
"status": "modified"
},
"amount": {
"previous": "5",
"current": "5",
"status": "unchanged"
},
"coin": {
"previous": "USDC",
"current": "USDC",
"status": "unchanged"
},
"network": {
"previous": "Ethereum Sepolia",
"current": "Ethereum Sepolia",
"status": "unchanged"
},
"address": {
"previous": "0x36279Ac046498bF0cb742622cCe22F3cE3c2AfD9",
"current": "0x36279Ac046498bF0cb742622cCe22F3cE3c2AfD9",
"status": "unchanged"
}
},
"has_monthly_payment": true
}
],
"swap_items": [
{
"id": "payroll_swap_item_n4rBJt7yuh6fVYCunWxQiJrd",
"lifi_quote_id": "f7800f87-ad2f-4411-b4e5-9a38eb03410e:0",
"created_at": 1750147470,
"updated_at": 1750159306,
"sent_at": null,
"merchant_id": "",
"payroll_id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"payroll": {
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750159306,
"canceled_at": 0,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"approval_status": "rejected",
"status": "rejected",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
"from_asset_id": "USDC-Ethereum-TEST",
"expected_from_amount": "0",
"estimated_from_amount": "32.776548",
"estimated_from_amount_usd": "33",
"actual_from_amount": "0",
"network_fee": "0.03",
"to_asset_id": "USDC-Solana-TEST",
"expected_to_amount": "32",
"estimated_to_amount": "32.173331",
"estimated_to_amount_min": "32.012464",
"estimated_to_amount_usd": "32",
"actual_to_amount": "0",
"status": "rejected"
}
]
}
}
6.3 Cancel Payroll
Cancel a payroll that is in draft or pending status.
For detailed API reference, see: Cancel Payroll API
Request:
curl --location --request POST 'https://api.martianpay.com/v1/payrolls/payroll_I6mzarIW0qZ4itwGg7xCa9EA/cancel' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'
Response:
{
"error_code": "success",
"msg": "success"
}
6.4 List Payroll Items
List payroll items across all payrolls or for a specific payroll.
For detailed API reference, see: List Payroll Items API
Request:
# List all payroll items
curl --location --request GET 'https://api.martianpay.com/v1/payrolls/items/list?page=0&page_size=10' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'
# List items for a specific payroll by external_id
curl --location --request GET 'https://api.martianpay.com/v1/payrolls/items/list?external_id=ORDER-2024-001&page=0&page_size=10' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'
Query Parameters:
page(required): Page number, starting from 0page_size(required): Items per page (1-50)start_date(optional): Filter by start date (YYYY-MM-DD)end_date(optional): Filter by end date (YYYY-MM-DD)employee_name(optional): Filter by employee nameexternal_id(optional): Filter by payroll external IDpayroll_id(optional): Filter by payroll IDitem_external_id(optional): Filter by payroll item external ID
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"payroll_items": [
{
"id": "payroll_item_QZmpUMN5R1Gxohg7ADKbsnTW",
"created_at": 1750147470,
"updated_at": 1750159306,
"payroll_id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"payroll": {
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750159306,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"external_id": "ORDER-2024-001",
"approval_status": "approved",
"status": "paid",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
"name": "John Doe",
"email": "john@example.com",
"phone": "13488888888",
"amount": "30",
"service_fee": "2",
"exchange_rate_to_usd": "1",
"coin": "USDT",
"network": "Tron",
"asset_id": "USDT-Tron",
"address": "TXkFhQXyCgDHcjVPnUTuexs5mrGPhTVXYD",
"address_verified": true,
"status": "paid",
"payment_method": "binance",
"is_binance": true,
"binance_tag": "BINANCE-001",
"binance_task_id": "binance_task_ABC123"
}
],
"total": 15
}
}
7. Webhook Events
MartianPay sends webhook notifications for important payroll and payroll item events. These webhooks help you track the status of your payroll transactions in real-time.
7.1 Payroll Events
| Event Type | Description | Triggered When |
|---|---|---|
payroll.created | Payroll batch created | A new payroll is created (draft status) |
payroll.approved | Payroll batch approved | Payroll is approved through approval workflow |
payroll.rejected | Payroll batch rejected | Payroll is rejected through approval workflow |
payroll.canceled | Payroll batch canceled | Payroll is canceled by user |
payroll.executing | Payroll batch executing | Payroll starts processing payments |
payroll.completed | Payroll batch completed | All payments in payroll completed successfully |
payroll.failed | Payroll batch failed | All payments in payroll failed |
7.2 Payroll Item Events
| Event Type | Description | Triggered When |
|---|---|---|
payroll_item.processing | Payroll item processing | Individual payment starts processing |
payroll_item.succeeded | Payroll item succeeded | Individual payment completed successfully |
payroll_item.failed | Payroll item failed | Individual payment failed |
payroll_item.address_verification_sent | Address verification sent | Micro-transfer sent for address verification |
payroll_item.address_verified | Address verified | Address successfully verified by recipient |
7.3 Webhook Payload Example
Here's an example of a payroll webhook event payload:
{
"id": "evt_056qw9fr9PndljykFUqSIf6t",
"object": "event",
"api_version": "2025-01-22",
"created": 1745580845,
"data": {
"object": {
"id": "payroll_I6mzarIW0qZ4itwGg7xCa9EA",
"created_at": 1750147470,
"updated_at": 1750159306,
"merchant_id": "accu_M7PTgveSgMtTtPHbjFgEtAlD",
"external_id": "ORDER-2024-001",
"approval_status": "approved",
"status": "executing",
"total_item_num": 2,
"total_amount": "35",
"total_service_fee": "4",
"currency": "USD"
},
"previous_attributes": {
"status": "approved",
"updated_at": 1750159300
}
},
"livemode": false,
"pending_webhooks": 0,
"type": "payroll.executing"
}
For payroll item events, the data.object will contain the PayrollItem object with details about the individual payment.
7.4 Webhook Event Flow
Typical Payroll Event Sequence:
payroll.created- Payroll is created in draft statuspayroll.approved- Payroll is approved (or auto-approved)payroll.executing- Payment processing begins- For each item:
payroll_item.processing- Item starts processingpayroll_item.address_verification_sent- (if needed) Verification sentpayroll_item.address_verified- (if needed) Address verifiedpayroll_item.succeededorpayroll_item.failed- Final item status
payroll.completedorpayroll.failed- Final payroll status
7.5 Recommended Event Monitoring
💡 Recommendation: We strongly recommend monitoring
payroll_item.*events for granular tracking of individual payments. Item-level events provide the most detailed and actionable information for your payroll operations.
Why Monitor Item Events?
| Payroll Events | Payroll Item Events |
|---|---|
| Track overall batch status | ✅ Track each individual payment |
| Limited actionability | ✅ Immediate notification for failures |
| Single notification per batch | ✅ Real-time updates per employee |
| Cannot identify which payment failed | ✅ Pinpoint exact failed payments |
Event Monitoring Strategy:
| Priority | Events to Monitor | Use Case |
|---|---|---|
| High | payroll_item.succeeded | Confirm individual payment success, update employee records |
| High | payroll_item.failed | Alert on payment failures, take immediate action |
| High | payroll_item.address_verification_sent | Notify employees to verify their address |
| High | payroll_item.address_verified | Confirm address verification completed |
| Medium | payroll.executing | Know when batch processing starts |
| Medium | payroll.completed | Confirm entire batch finished |
| Low | payroll.created | Track batch creation (optional) |
7.6 Best Practices
-
Prioritize payroll_item events - Monitor
payroll_item.*events for real-time, actionable insights on individual payments. These events let you:- Immediately identify and retry failed payments
- Track payment status per employee
- Send targeted notifications to employees
- Update payment records at a granular level
-
Handle idempotency - Store the event ID (
evt_*) to prevent duplicate processing of the same webhook -
Monitor address verification flow - Track
payroll_item.address_verification_sentandpayroll_item.address_verifiedevents to:- Send reminder emails to employees who haven't verified
- Track verification completion rates
- Alert employees about pending verifications
-
Reconcile with external_id - Use the
external_idfield to match webhook events with your internal payroll records -
Implement automated retry logic - For
payroll_item.failedevents:- Automatically retry transient failures (network issues, temporary wallet unavailability)
- Alert administrators for permanent failures (invalid address, insufficient balance)
- Track retry attempts to prevent infinite loops
-
Build employee dashboards - Use item events to show real-time payment status to employees in your system
For more details on webhook setup, signature verification, and complete event schemas, see the Webhook Events documentation.