Skip to main content

Merchant API

How service providers register, manage, and receive payments through ItPay.

The Merchant API is the set of operations a service provider (seller) uses to onboard onto ItPay, publish their ServiceManifest, manage pricing and channels, receive webhook notifications for completed payments, and reconcile transactions.


Onboarding Flow

Every merchant follows the same onboarding lifecycle:

┌──────────────────────┐
│ 1. Create Account │ → itpay.ai/dashboard or API
│ & Generate API Key │
└──────────┬───────────┘

┌──────────▼───────────┐
│ 2. Register │ → POST /v1/services
│ ServiceManifest │
└──────────┬───────────┘

┌──────────▼───────────┐
│ 3. Configure │ → accepted_channels, pricing,
│ Channels & Pricing │ settlement_currency, webhook URL
└──────────┬───────────┘

┌──────────▼───────────┐
│ 4. Activate │ → PATCH /v1/services/{'{'}id{'}'}/activate
│ & Go Live │
└──────────┬───────────┘

┌──────────▼───────────┐
│ 5. Receive Payments │ → Webhook notifications
│ via Webhooks │ + Dashboard analytics
└──────────────────────┘

1. Merchant Account & API Keys

Creating an Account

Register at itpay.ai. After verification, you receive:

CredentialFormatPurpose
Merchant IDm_XXXXXXXXIdentifies your account in API calls
Live API Keysk_liv_XXXXXXXXXXXXXXXXProduction requests
Test API Keysk_test_XXXXXXXXXXXXXXXXSandbox testing
Webhook Secretwhsec_XXXXXXXXXXXXXXXXVerifies webhook payloads

API Key Security

┌─────────────────────────┐
│ API Keys │
│ │
│ ┌─────────────────────┐│
│ │ sk_liv_... ││ ← Full access (production)
│ └─────────────────────┘│
│ │ sk_test_... ││ ← Sandbox only
│ └─────────────────────┘│
│ │ whsec_... ││ ← Webhook verification only
│ └─────────────────────┘│
│ │
│ Actions: create / │
│ revoke / rotate │
│ IP whitelist: ✓ │
└─────────────────────────┘
  • Rotate API keys periodically via the dashboard
  • Set IP whitelists per key for additional security
  • Use separate keys for development and production

Authentication

All API requests include the API key in the Authorization header:

Authorization: Bearer sk_liv_XXXXXXXXXXXXXXXX

2. ServiceManifest Management

Creating a Manifest

POST /v1/services
Authorization: Bearer sk_liv_XXXXXXXXXXXXXXXX

{
"name": "AI Document Summary",
"description": "Summarize any PDF or text in seconds.",
"payment_methods": {
"one_time": true,
"cumulative": false,
"subscription": true
},
"pricing": {
"one_time": [
{ "amount": 99, "currency": "USD", "label": "per summary" }
],
"subscription": [
{
"plan_id": "plan_starter",
"name": "Starter",
"amount": 999,
"currency": "USD",
"interval": "monthly",
"features": ["50 summaries/mo"]
}
]
},
"accepted_channels": ["alipay", "wechat", "promptpay"],
"qr_mode": "dynamic",
"settlement_currency": "USD",
"endpoint": "https://api.myservice.ai/itpay/webhook",
"tags": ["summarization", "ai", "document"]
}

Response: HTTP 201 with the full ServiceManifest object, including the generated id (UUIDv7).

Reading Manifests

# List all manifests
GET /v1/services
Authorization: Bearer sk_liv_XXXXXXXXXXXXXXXX

# Get a specific manifest
GET /v1/services/{'{'}id{'}'}
Authorization: Bearer sk_liv_XXXXXXXXXXXXXXXX

Updating a Manifest

Re-POST to the same name or id with updated fields:

POST /v1/services
Authorization: Bearer sk_liv_XXXXXXXXXXXXXXXX

{
"id": "01J7XYKZ1A2B3C4D5E6F7G8H9I",
"name": "AI Document Summary",
"pricing": {
"one_time": [
{ "amount": 129, "currency": "USD", "label": "per summary" }
]
}
}

Only the fields you include are updated. Omitted fields retain their current values.

Manifest Lifecycle

┌──────────┐
│ draft │ ← Created but not visible in discovery
└────┬─────┘
│ activate

┌──────────┐
│ active │ ← Visible in search, MCP tools/list
└────┬─────┘
│ pause

┌──────────┐
│ paused │ ← Hidden from discovery, active subs continue
└────┬─────┘
│ deprecate

┌─────────────┐
│ deprecated │ ← Still visible, labelled "deprecated"
└──────┬──────┘
│ delete

┌──────────┐
│ deleted │ ← Permanently removed
└──────────┘
ActionEndpointDescription
ActivatePATCH /v1/services/{'{'}id{'}'}/activateMake manifest visible in discovery
PausePATCH /v1/services/{'{'}id{'}'}/pauseTemporarily hide from discovery
ResumePATCH /v1/services/{'{'}id{'}'}/resumeRestore visibility
DeprecatePATCH /v1/services/{'{'}id{'}'}/deprecateMark as deprecated
DeleteDELETE /v1/services/{'{'}id{'}'}Permanently remove

3. Webhook Configuration

The endpoint URL in your ServiceManifest is where ItPay delivers all payment events. Each webhook is a signed HTTP POST with an HMAC-SHA256 signature.

Webhook Event Types

Event TypeDescriptionTrigger
payment_intent.succeededA payment completed successfullyDeliver your service
payment_intent.failedA payment attempt failedNotify the user
payment_intent.expiredQR code expired before paymentAsk user to retry
subscription.renewedA subscription auto-renewedExtend access
subscription.cancelledA subscription was cancelledRevoke access
refund.succeededA refund was processedUpdate your records
refund.failedA refund failedInvestigate and retry
revocation.succeededAccess was revokedConfirm access removal

Webhook Payload Format

{
"id": "evt_01J7XZ1A2B3C4D5E6F7G8H9IK",
"type": "payment_intent.succeeded",
"created": "2026-05-27T09:30:00Z",
"data": {
"id": "pi_01J7XZ1A2B3C4D5E6F7G8H9IK",
"amount": { "value": 699, "currency": "CNY" },
"settlement": { "value": 99, "currency": "USD", "rate": 0.1416 },
"channel": "alipay",
"payer": {
"agent_id": "agent_cli_a1b2c3d4",
"human_id": "user_abc_789"
},
"metadata": {
"session_id": "sess_xyz_456"
}
}
}

Webhook Retry Schedule

If your endpoint doesn't return 200 OK within 5 seconds, ItPay retries:

AttemptDelay
1st10 seconds
2nd1 minute
3rd10 minutes
4th1 hour
5th6 hours
6th24 hours (final)

After the 6th failure, the webhook is dropped permanently. Monitor for dropped events via the dashboard.

Best Practices

  • Verify signatures — Always validate the X-ItPay-Signature header before acting on a payload
  • Respond 200 quickly — Acknowledge receipt within 5s, process asynchronously
  • Idempotent handlers — Check event.id before processing; webhooks may be delivered more than once
  • Log request_id — Include request_id in all logs for support traceability

See the Security Model page for signature verification details and Error Codes for webhook error handling.


4. Transaction Management

Viewing Transactions

# List all payment intents
GET /v1/payment-intents?limit=20&offset=0
Authorization: Bearer sk_liv_XXXXXXXXXXXXXXXX

# Filter by status
GET /v1/payment-intents?status=succeeded&created_after=2026-05-01

# Filter by channel
GET /v1/payment-intents?channel=alipay

Reconciliation

For each completed payment, the webhook payload includes both the payer amount (what the user paid, in their local currency) and the settlement amount (what you receive, converted to your settlement currency):

{
"amount": { "value": 699, "currency": "CNY" },
"settlement": { "value": 99, "currency": "USD", "rate": 0.1416 }
}

Reconciliation reports are available via:

GET /v1/reconciliation?date=2026-05-27
Authorization: Bearer sk_liv_XXXXXXXXXXXXXXXX

Returns a summary of all transactions settled on the given date, including fees, FX rates, and payout amounts.


5. Dashboard

The merchant dashboard at itpay.ai/dashboard provides:

┌──────────────────────────────────────────┐
│ 📊 Dashboard │
│ │
│ Revenue Today Active Channels │
│ $124.50 3 / 6 │
│ ┌────────────┐ ┌──────────────────┐ │
│ │ ██████████ │ │ Alipay ¥1,420 │ │
│ │ ██████ │ │ WeChat ¥890 │ │
│ │ ████ │ │ PromptPay ฿2,100 │ │
│ └────────────┘ └──────────────────┘ │
│ │
│ Recent Transactions │
│ ┌────────────────────────────────┐ │
│ │ ¥6.99 Alipay ✓ succeeded │ │
│ │ ¥3.50 WeChat ✓ succeeded │ │
│ │ ¥12.00 PromptPay ✕ expired │ │
│ └────────────────────────────────┘ │
│ │
│ [Webhook Logs] [API Keys] [Reports] │
└──────────────────────────────────────────┘
Dashboard SectionDescription
RevenueReal-time earnings by channel (today, this week, this month)
TransactionsLive feed of all payment intents with status
Channel HealthPer-channel success rates and error counts
Webhook LogsRecent webhook delivery attempts and their HTTP status codes
API Key ManagerGenerate, revoke, and rotate keys with IP whitelisting
ReconciliationDownloadable CSV reports for accounting

6. Channel & Settlement Configuration

Adding a Channel

Each channel requires separate credentials (managed via the Channel Adapter). From the dashboard or API:

POST /v1/channels
Authorization: Bearer sk_liv_XXXXXXXXXXXXXXXX

{
"channel": "wechat",
"credentials": {
"mchid": "1230000109",
"appid": "wx1234567890abcdef",
"api_certificate_pem": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
"apiv3_key": "32bytestringfortest..."
},
"notify_url": "https://api.myservice.itpay/v1/wechat-callback"
}

Settlement Preferences

Configure per-channel settlement via your merchant profile:

PATCH /v1/merchant/settlement
Authorization: Bearer sk_liv_XXXXXXXXXXXXXXXX

{
"preferred_settlement_currency": "USD",
"auto_withdrawal": true,
"withdrawal_threshold": { "value": 10000, "currency": "USD" }
}

Summary: Merchant API Operations

OperationEndpointDescription
Register manifestPOST /v1/servicesCreate a new ServiceManifest
List manifestsGET /v1/servicesList all your manifests
Get manifestGET /v1/services/{'{'}id{'}'}Get a single manifest
Update manifestPOST /v1/servicesUpdate an existing manifest
ActivatePATCH /v1/services/{'{'}id{'}'}/activateGo live
Pause / ResumePATCH /v1/services/{'{'}id{'}'}/pauseToggle visibility
Deprecate / Delete`PATCHDELETE /v1/services/{id}`
List transactionsGET /v1/payment-intentsView payment history
Get reconciliationGET /v1/reconciliationDownload settlement report
Manage channelsPOST /v1/channelsAdd/configure payment channels
Manage API keysDashboardGenerate, rotate, revoke keys
Webhook logsDashboardMonitor delivery status

7. Payment Lifecycle (Swimlane)

The complete journey of a payment — from customer scan to merchant fulfillment — spans four participants:

Merchant ItPay Payment Channel Customer
│ │ │ │
│ 1. Create │ │ │
│ PaymentIntent │ │ │
│ ─────────────────►│ │ │
│ │ 2. Translate │ │
│ │ via Adapter │ │
│ │ ──────────────────►│ │
│ │ │ │
│ │ 3. QR Response │ │
│ │ ◄──────────────────│ │
│ │ │ │
│ 4. Return QR │ │ │
│ ◄─────────────────│ │ │
│ │ │ │
│ │ │ 5. Scan QR │
│ │ │ ◄─────────────────│
│ │ │ │
│ │ │ 6. Pay │
│ │ │ ◄─────────────────│
│ │ │ │
│ │ 7. Callback │ │
│ │ ◄──────────────────│ │
│ │ │ │
│ 8. Webhook │ │ │
│ ◄─────────────────│ │ │
│ │ │ │
│ 9. Verify HMAC │ │ │
│ ──┐ │ │ │
│ │ (async) │ │ │
│ 10. Deliver │ │ │
│ Service │ │ │
│ 11. Reconcile │ │ │
│ ─────────────────►│ │ │

Step-by-Step

StepActorActionDetails
1MerchantPOST /v1/payment-intentsCreates a payment request with amount, channel, metadata
2ItPayChannel AdapterTranslates to channel-specific API format (WeChat RSA-SHA256 / Alipay RSA2)
3ChannelQR ResponseReturns code_url (WeChat) or qr_code (Alipay EMVCo TLV)
4ItPay → MerchantNormalized responseReturns { qr_code, id, expires_at } in unified format
5CustomerScans QROpens payment app (WeChat/Alipay/Bank app)
6CustomerAuthorizes paymentConfirms amount and pays within the app
7Channel → ItPayCallbackSends encrypted/signed notification to ItPay's callback endpoint
8ItPay → MerchantWebhookSends payment_intent.succeeded with HMAC-SHA256 signature
9MerchantVerifies HMACValidates X-ItPay-Signature header against webhook secret
10MerchantDelivers serviceFulfills the order (async processing recommended)
11MerchantReconcilesMatches webhook data against settlement reports

Complete HTTP for Payment Creation

Request:

POST /v1/payment-intents
Authorization: Bearer sk_liv_abc123def456
Content-Type: application/json
Idempotency-Key: idem_01J8ABC123

{
"service_id": "01J7XYKZ1A2B3C4D5E6F7G8H9I",
"amount": { "value": 699, "currency": "CNY" },
"channel": "alipay",
"metadata": {
"session_id": "sess_xyz_456",
"user_id": "user_abc_789"
},
"expires_in": 900
}

Response (201 Created):

{
"id": "pi_01J7XZ1A2B3C4D5E6F7G8H9IK",
"status": "pending",
"amount": { "value": 699, "currency": "CNY" },
"qr_code": "00020101021229370016A000000677010112...",
"expires_at": "2026-05-27T09:45:00Z",
"created": "2026-05-27T09:30:00Z"
}

8. Webhook Event Handling Reference

For each webhook event type, the merchant should follow the recommended action:

Event TypeRecommended ActionIdempotency CheckNotes
payment_intent.succeededDeliver the purchased service/goodsCheck event.data.idThis is the only trigger to fulfill — see swimlane above
payment_intent.failedNotify the user and offer retryLog for analyticsNo settlement occurred; no action on channel side needed
payment_intent.expiredAsk the user to initiate a fresh paymentCreate new PaymentIntentQR code lifetime has elapsed; old code_url/qr_code is invalid
subscription.renewedExtend service access for the next billing periodCheck subscription period datesContains current_period_end and next_billing_date
subscription.cancelledSchedule access revocation at period endUpdate user's sub statusService access continues until the current billing period ends
refund.succeededMark the original payment as refundedUpdate transaction ledgerOriginal payment_intent.id is in event.data.refunded_payment_intent
refund.failedInvestigate why refund failed, retry if possibleCheck channel error detailsCommon causes: insufficient balance, expired refund window
revocation.succeededConfirm access has been revokedRemove service entitlementsFinal confirmation in the revocation lifecycle

Webhook Verification (Complete HTTP)

Incoming webhook request:

POST https://api.myservice.ai/itpay/webhook
Content-Type: application/json
X-ItPay-Signature: t=1715712345,v1=f47ac10b58cc4372a5f1c9c07c2f3d8a
X-ItPay-Request-Id: req_01J7XZ1A2B3C4D5E6F7G8H9IK
User-Agent: ItPay-Webhook/1.0

{
"id": "evt_01J7XZ1A2B3C4D5E6F7G8H9IK",
"type": "payment_intent.succeeded",
"created": "2026-05-27T09:30:00Z",
"data": {
"id": "pi_01J7XZ1A2B3C4D5E6F7G8H9IK",
"amount": { "value": 699, "currency": "CNY" },
"settlement": { "value": 99, "currency": "USD", "rate": 0.1416 },
"channel": "alipay"
}
}

Verification logic:

import hmac, hashlib, time

def verify_webhook(payload: bytes, signature_header: str, secret: str) -> bool:
# Signature format: "t={timestamp},v1={hex}"
parts = dict(p.split("=") for p in signature_header.split(","))
timestamp = parts["t"]
expected_sig = parts["v1"]

# Build signing payload: timestamp.payload
signed_payload = f"{timestamp}.{payload.decode('utf-8')}".encode("utf-8")

# Compute HMAC-SHA256
computed = hmac.new(
secret.encode("utf-8"),
signed_payload,
hashlib.sha256
).hexdigest()

# Constant-time comparison
return hmac.compare_digest(computed, expected_sig)

Acknowledge receipt (respond within 5s):

HTTP/1.1 200 OK
Content-Type: application/json

{ "received": true }

9. Settlement & Reconciliation Flow

After a channel settles funds to the merchant's account, ItPay generates a reconciliation report:

Channel ItPay Merchant
│ │ │
│ 1. Settle funds │ │
│ ───────────────►│ │
│ │ 2. Record │
│ │ settlement │
│ │ ──┐ │
│ │ │ │
│ │ 3. Generate │
│ │ report │
│ │ ◄─┘ │
│ │ │
│ │ 4. Recon Report │
│ │ Ready │
│ │───────────────────►│
│ │ │
│ │ 5. Webhook: │
│ │ settlement │
│ │ .completed │
│ │───────────────────►│
│ │ │
│ │ 6. GET │
│ │ /reconciliation │
│ │◄───────────────────│
│ │ │
│ │ 7. CSV/JSON │
│ │ Report │
│ │───────────────────►│

Complete HTTP for Reconciliation

Get daily reconciliation report:

GET /v1/reconciliation?date=2026-05-27&format=csv
Authorization: Bearer sk_liv_abc123def456

Response (CSV):

date,payment_intent_id,channel,payer_amount,payer_currency,settlement_amount,settlement_currency,fx_rate,fee,payout
2026-05-27,pi_01J7XZ1A2B3C4D5E6F7G8H9IK,alipay,699,CNY,99,USD,0.1416,2.97,96.03
2026-05-27,pi_01J7XZ2B3C4D5E6F7G8H9IJ,wechat,1000,CNY,141.60,USD,0.1416,4.25,137.35

Response (JSON):

GET /v1/reconciliation?date=2026-05-27&format=json
Authorization: Bearer sk_liv_abc123def456

{
"date": "2026-05-27",
"merchant_id": "m_XXXXXXXX",
"summary": {
"total_transactions": 42,
"total_payer_amount": { "value": 42000, "currency": "CNY" },
"total_settlement": { "value": 5947.20, "currency": "USD" },
"total_fees": { "value": 178.42, "currency": "USD" },
"net_payout": { "value": 5768.78, "currency": "USD" }
},
"transactions": [
{
"payment_intent_id": "pi_01J7XZ1A2B3C4D5E6F7G8H9IK",
"channel": "alipay",
"payer_amount": { "value": 699, "currency": "CNY" },
"settlement": { "value": 99, "currency": "USD", "rate": 0.1416 },
"fee": { "value": 2.97, "currency": "USD" },
"payout": { "value": 96.03, "currency": "USD" },
"settled_at": "2026-05-27T14:00:00Z"
}
]
}

10. Error Handling & Recovery

Webhook Delivery Failures

Failure ScenarioRetry BehaviorRecommended Action
5s timeoutRetry on schedule (10s → 1m → 10m → 1h → 6h → 24h)Ensure endpoint responds quickly; process async
Non-200 responseSame retry schedule as timeoutReturn 200 OK with { "received": true } immediately
Network errorSame retry scheduleCheck firewall rules; ensure endpoint is publicly reachable
6th failureWebhook dropped permanentlyMonitor webhook logs in dashboard; manually re-process via API
# Fetch dropped webhooks for manual reprocessing
GET /v1/webhooks/dropped?date=2026-05-27
Authorization: Bearer sk_liv_abc123def456

# Replay a specific webhook
POST /v1/webhooks/evt_01J7XZ1A2B3C4D5E6F7G8H9IK/replay
Authorization: Bearer sk_liv_abc123def456

Invalid Credentials

ScenarioHTTP StatusError CodeRecovery
Missing or malformed Authorization header401 Unauthorizedauth_invalidInclude Authorization: Bearer <live_key>
API key revoked401 Unauthorizedauth_revokedGenerate new key in dashboard
API key expired401 Unauthorizedauth_expiredRotate key before expiry (keys valid 365 days)
IP not whitelisted403 Forbiddenip_not_allowedAdd caller IP to key's whitelist
Invalid webhook signature400 Bad Requestsignature_mismatchVerify webhook secret matches whsec_* in dashboard

Channel Outage Scenarios

Merchant ItPay Channel
│ │ │
│ POST PaymentIntent │
│ ────────────────►│ │
│ │ POST to Channel │
│ │ ──────────────────►│
│ │ ❌ 503/Timeout │
│ │◄───────────────────│
│ │ │
│ │ Retry #1 (1s) │
│ │ ──────────────────►│
│ │ ❌ 503/Timeout │
│ │◄───────────────────│
│ │ │
│ │ Retry #2 (5s) │
│ │ ──────────────────►│
│ │ ❌ 503/Timeout │
│ │◄───────────────────│
│ │ │
│ 503 Service │ │
│ Unavailable │ │
│◄─────────────────│ │
│ │ │
│ Fallback: retry │ │
│ with diff channel│ │

Channel outage response:

HTTP/1.1 503 Service Unavailable
Content-Type: application/json
Retry-After: 30

{
"error": {
"code": "channel_unavailable",
"message": "Alipay is currently unavailable. Please try again later or use an alternative channel.",
"channel": "alipay",
"retry_after": 30,
"alternatives": ["wechat", "promptpay"]
}
}

11. Dashboard Operations — API Equivalents

While the dashboard provides a visual interface, all operations have API equivalents:

Viewing Transactions

# Paginated transaction feed (equivalent to dashboard table)
GET /v1/payment-intents?limit=50&offset=0&sort=created:desc
Authorization: Bearer sk_liv_abc123def456
{
"data": [
{
"id": "pi_01J7XZ1A2B3C4D5E6F7G8H9IK",
"status": "succeeded",
"amount": { "value": 699, "currency": "CNY" },
"channel": "alipay",
"created": "2026-05-27T09:30:00Z",
"payer": { "agent_id": "agent_cli_a1b2c3d4" }
}
],
"pagination": {
"total": 142,
"limit": 50,
"offset": 0,
"next_offset": 50
}
}

Generating Reports

# Download CSV report filtered by date range and channels
GET /v1/reconciliation/export?start=2026-05-01&end=2026-05-27&channels=alipay,wechat&format=csv
Authorization: Bearer sk_liv_abc123def456

Webhook Logs

# View recent webhook delivery attempts
GET /v1/webhooks/logs?limit=20&status=failed
Authorization: Bearer sk_liv_abc123def456
{
"data": [
{
"event_id": "evt_01J7XZ1A2B3C4D5E6F7G8H9IK",
"type": "payment_intent.succeeded",
"endpoint": "https://api.myservice.ai/itpay/webhook",
"attempts": 6,
"last_attempt": "2026-05-27T10:30:00Z",
"last_status_code": 504,
"status": "dropped"
}
]
}

Managing API Keys via API

# List keys
GET /v1/api-keys
Authorization: Bearer sk_liv_abc123def456

# Create new key
POST /v1/api-keys
Authorization: Bearer sk_liv_abc123def456
Content-Type: application/json

{
"label": "CI/CD Pipeline",
"permissions": ["payment_intents:read", "webhooks:read"],
"ip_whitelist": ["203.0.113.0/24"]
}

# Revoke key
DELETE /v1/api-keys/k_01J7XYKZ1A2B3C4D5E6F7G8H9I
Authorization: Bearer sk_liv_abc123def456

Next Steps