# API Authentication Source: https://docs.ceypay.io/api/v1/authentication Learn how CeyPay's HMAC-SHA256 signature authentication secures API communication between your application and our servers. ## Overview Every API request must include three headers: Your API key ID only (format: `ak_live_xxx`) Current Unix timestamp in milliseconds HMAC-SHA256 signature of the request The `x-api-key` header should contain ONLY the key ID, NOT the full API key. The secret key is never transmitted over the wire. ## Getting Your API Key **API Key Format**: `ak_live_abc123.sk_live_xyz789` * First part (`ak_live_abc123`): Public key ID - sent in `x-api-key` header * Second part (`sk_live_xyz789`): Secret key - used locally to derive signing key, NEVER transmitted ## Signature Calculation The signature is calculated using a **derived signing key** (hash of your secret): ``` signingKey = SHA256(secret_key) message = timestamp + method + path + body signature = HMAC-SHA256(message, signingKey) ``` ### Why Use a Derived Key? This design ensures your secret key is **never transmitted** over the network: * You send only the key ID in `x-api-key` * The signature proves you possess the secret without revealing it * Even if an attacker intercepts the request, they cannot forge new requests ### Components: 1. **signingKey**: SHA256 hash of your secret key (computed locally) 2. **timestamp**: Unix timestamp in milliseconds (same value as `x-timestamp` header) 3. **method**: HTTP method in UPPERCASE (`GET`, `POST`, `PATCH`, `DELETE`) 4. **path**: Full request path including query parameters (e.g., `/v1/payments?page=1`) 5. **body**: Request body as JSON string (empty string for GET/DELETE requests) ## Implementation Examples ```javascript Node.js theme={null} const crypto = require('crypto'); // Your full API key (store securely, e.g., in environment variables) const API_KEY = 'ak_live_abc123.sk_live_xyz789'; const [keyId, secret] = API_KEY.split('.'); // Derive signing key from secret (do this once, reuse for all requests) const signingKey = crypto.createHash('sha256').update(secret).digest('hex'); function generateSignature(timestamp, method, path, body) { const message = timestamp + method + path + body; const signature = crypto .createHmac('sha256', signingKey) .update(message) .digest('hex'); return signature; } // Example: POST /v1/payment const timestamp = Date.now().toString(); const method = 'POST'; const path = '/v1/payment'; const body = JSON.stringify({ amount: 100, currency: 'USDT', goods: [{ name: 'Product', description: 'Test product' }] }); const signature = generateSignature(timestamp, method, path, body); // Make the request const axios = require('axios'); const response = await axios.post('https://api.ceypay.io/v1/payment', body, { headers: { 'Content-Type': 'application/json', 'x-api-key': keyId, // Only the key ID, NOT the full key! 'x-timestamp': timestamp, 'x-signature': signature } }); ``` ```python Python theme={null} import hmac import hashlib import time import json import requests # Your full API key (store securely) API_KEY = 'ak_live_abc123.sk_live_xyz789' key_id, secret = API_KEY.split('.') # Derive signing key from secret signing_key = hashlib.sha256(secret.encode()).hexdigest() def generate_signature(timestamp, method, path, body): message = f"{timestamp}{method}{path}{body}" signature = hmac.new( signing_key.encode(), message.encode(), hashlib.sha256 ).hexdigest() return signature # Example: POST /v1/payment timestamp = str(int(time.time() * 1000)) method = 'POST' path = '/v1/payment' body_dict = { 'amount': 100, 'currency': 'USDT', 'goods': [{'name': 'Product', 'description': 'Test product'}] } body = json.dumps(body_dict) signature = generate_signature(timestamp, method, path, body) # Make the request response = requests.post( 'https://api.ceypay.io/v1/payment', json=body_dict, headers={ 'x-api-key': key_id, # Only the key ID! 'x-timestamp': timestamp, 'x-signature': signature } ) ``` ```php PHP theme={null} 100, 'currency' => 'USDT', 'goods' => [ ['name' => 'Product', 'description' => 'Test product'] ] ]; $body = json_encode($bodyArray); $signature = generateSignature($timestamp, $method, $path, $body, $signingKey); // Make the request $ch = curl_init('https://api.ceypay.io/v1/payment'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $body); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'x-api-key: ' . $keyId, // Only the key ID! 'x-timestamp: ' . $timestamp, 'x-signature: ' . $signature ]); $response = curl_exec($ch); curl_close($ch); ?> ``` ```ruby Ruby theme={null} require 'openssl' require 'json' require 'net/http' require 'uri' # Your full API key (store securely) api_key = 'ak_live_abc123.sk_live_xyz789' key_id, secret = api_key.split('.') # Derive signing key from secret signing_key = Digest::SHA256.hexdigest(secret) def generate_signature(timestamp, method, path, body, signing_key) message = "#{timestamp}#{method}#{path}#{body}" OpenSSL::HMAC.hexdigest('SHA256', signing_key, message) end # Example: POST /v1/payment timestamp = (Time.now.to_f * 1000).to_i.to_s method = 'POST' path = '/v1/payment' body_hash = { amount: 100, currency: 'USDT', goods: [{ name: 'Product', description: 'Test product' }] } body = body_hash.to_json signature = generate_signature(timestamp, method, path, body, signing_key) # Make the request uri = URI('https://api.ceypay.io/v1/payment') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri.path) request['Content-Type'] = 'application/json' request['x-api-key'] = key_id # Only the key ID! request['x-timestamp'] = timestamp request['x-signature'] = signature request.body = body response = http.request(request) ``` ## Common Mistakes & Troubleshooting ### 1. Sending Full API Key in Header ❌ **Wrong**: Sending the full key including secret ```javascript theme={null} headers: { 'x-api-key': 'ak_live_abc123.sk_live_xyz789' // Wrong! } ``` ✅ **Correct**: Send only the key ID ```javascript theme={null} headers: { 'x-api-key': 'ak_live_abc123' // Correct! } ``` ### 2. Using Raw Secret Instead of Derived Key ❌ **Wrong**: Signing with raw secret ```javascript theme={null} const signature = crypto.createHmac('sha256', secret).update(message).digest('hex'); ``` ✅ **Correct**: Sign with derived key (SHA256 hash of secret) ```javascript theme={null} const signingKey = crypto.createHash('sha256').update(secret).digest('hex'); const signature = crypto.createHmac('sha256', signingKey).update(message).digest('hex'); ``` ### 3. Incorrect Timestamp Format ❌ **Wrong**: Using seconds instead of milliseconds ```javascript theme={null} const timestamp = Math.floor(Date.now() / 1000); // Wrong! ``` ✅ **Correct**: Use milliseconds ```javascript theme={null} const timestamp = Date.now().toString(); // Correct! ``` ### 4. Incorrect Message Concatenation ❌ **Wrong**: Adding spaces or separators ```javascript theme={null} const message = `${timestamp} ${method} ${path} ${body}`; // Wrong! ``` ✅ **Correct**: Direct concatenation with no separators ```javascript theme={null} const message = timestamp + method + path + body; // Correct! ``` ### 5. Query Parameters in Path For GET requests with query parameters, include them in the path: ✅ **Correct**: ```javascript theme={null} const path = '/v1/payment/list?page=2&pageSize=50'; const body = ''; // Empty for GET requests const message = timestamp + 'GET' + path + body; ``` ### 6. JSON Body Formatting Ensure the body is stringified exactly as sent in the request: ✅ **Correct**: ```javascript theme={null} const bodyObject = { amount: 100, currency: 'USDT', goods: [...] }; const bodyString = JSON.stringify(bodyObject); // Use bodyString for both signature and request body const signature = generateSignature(timestamp, method, path, bodyString); ``` ### 7. Timestamp Expiration Timestamps are valid for **5 minutes**. If you get a timestamp error: * Ensure your server's clock is synchronized (use NTP) * Generate the timestamp immediately before making the request * Don't reuse old timestamps ## Security Best Practices 1. **Never expose your secret key** * Don't commit it to version control * Use environment variables * Rotate keys if compromised 2. **Use HTTPS only** * Never send API requests over HTTP * Validate SSL certificates 3. **Implement timestamp validation** * Reject requests with timestamps older than 5 minutes * Prevents replay attacks 4. **Log signature failures** * Monitor for unusual patterns * Could indicate attempted attacks 5. **Rotate API keys periodically** * Recommended: Every 90 days * Immediately if compromised 6. **Store the derived signing key securely** * Compute it once at application startup * Keep it in memory, don't log it ## Testing Your Implementation Use the webhook test endpoint to verify your signature calculation: ```bash theme={null} # First, derive your signing key SIGNING_KEY=$(echo -n "sk_live_xyz789" | sha256sum | cut -d' ' -f1) # Then create signature TIMESTAMP=$(date +%s000) MESSAGE="${TIMESTAMP}POST/v1/webhooks/test{\"webhookUrl\": \"https://your-app.com/webhook\"}" SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$SIGNING_KEY" | cut -d' ' -f2) curl -X POST https://api.ceypay.io/v1/webhooks/test \ -H "Content-Type: application/json" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: $TIMESTAMP" \ -H "x-signature: $SIGNATURE" \ -d '{"webhookUrl": "https://your-app.com/webhook"}' ``` If you get a 401 Unauthorized, check: 1. `x-api-key` contains ONLY the key ID (no `.sk_live_...` part) 2. Signing key is SHA256 hash of your secret 3. Timestamp is current (within 5 minutes) and in milliseconds 4. Signature calculation matches exactly 5. HTTP method is uppercase 6. Path includes query parameters if any ## Error Responses ### 401 Unauthorized - Invalid x-api-key Format ```json theme={null} { "statusCode": 401, "message": "Invalid x-api-key format: send only the key ID (ak_live_xxx), not the full key", "error": "Unauthorized" } ``` **Fix**: Send only the key ID in `x-api-key`, not the full `keyId.secret` format. ### 401 Unauthorized - Invalid Signature ```json theme={null} { "statusCode": 401, "message": "Invalid signature", "error": "Unauthorized" } ``` **Fix**: Ensure you're using the derived signing key (SHA256 hash of secret) for HMAC. ### 401 Unauthorized - Timestamp Outside Valid Window ```json theme={null} { "statusCode": 401, "message": "Request timestamp outside valid window", "error": "Unauthorized" } ``` **Fix**: Ensure timestamp is current (within 5 minutes) and in milliseconds. ### 401 Unauthorized - Invalid API Key ```json theme={null} { "statusCode": 401, "message": "Invalid or inactive API key", "error": "Unauthorized" } ``` **Fix**: Verify your API key ID is correct and hasn't been revoked. ## Rate Limits API requests are rate-limited per endpoint. See [rate-limits.md](/api/v1/rate-limits) for details. Rate limit headers are included in every response: ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1640000000 ``` ## Need Help? Get started with your first payment in minutes Understand and troubleshoot API errors Reach out to our team for assistance # Direct Debit API Source: https://docs.ceypay.io/api/v1/direct-debit Create and manage Direct Debit contracts for recurring payments and on-demand charges through CeyPay. ## Endpoints | Method | Endpoint | Description | Rate Limit | | ------ | ------------------------------------- | -------------------------------- | ----------- | | GET | `/v1/direct-debit/scenario-code/list` | Get supported scenario codes | 200 req/min | | POST | `/v1/direct-debit` | Create a new contract | 100 req/min | | GET | `/v1/direct-debit/list` | List contracts | 100 req/min | | GET | `/v1/direct-debit/:id` | Get contract details | 100 req/min | | POST | `/v1/direct-debit/:id/sync` | Query/sync contract status | 50 req/min | | POST | `/v1/direct-debit/:id/terminate` | Terminate a contract | 50 req/min | | POST | `/v1/direct-debit/:id/payment` | Execute payment against contract | 100 req/min | *** ## Get Scenario Codes Retrieves a list of supported scenario codes for direct debit contracts. Use these IDs when creating a contract. Scenarios define the type of service and transaction limits. ``` GET /v1/direct-debit/scenario-code/list ``` ### Authentication Requires [HMAC authentication](/api/v1/authentication) with headers: * `x-api-key`: Your API key ID only (e.g., `ak_live_xxx`) * `x-timestamp`: Current Unix timestamp in milliseconds * `x-signature`: HMAC-SHA256 signature (using derived signing key) ### Query Parameters | Parameter | Type | Required | Description | | ---------- | ------ | -------- | -------------------------------------------------------------------- | | `provider` | string | No | Filter by payment provider: `BINANCE_PAY`, `BYBIT_PAY`, `KUCOIN_PAY` | | `active` | string | No | Filter by active status (true/false, default: true) | ### Example Request ```bash theme={null} curl "https://api.ceypay.io/v1/direct-debit/scenario-code/list?provider=BINANCE_PAY" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" ``` ### Example Response ```json theme={null} { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440003", "scenarioId": "12345", "scenarioName": "Subscription Service", "paymentProvider": "BINANCE_PAY", "maxLimit": 1000, "isActive": true } ] } ``` ### Error Responses | Status | Description | | ------ | --------------------------------------- | | 401 | Unauthorized - invalid HMAC signature | | 429 | Too many requests - rate limit exceeded | *** ## Create Contract Creates a pre-authorization contract that users can sign to enable on-demand payments. Supports multiple payment providers (currently: BINANCE\_PAY). Returns QR code and deep link for user approval. ``` POST /v1/direct-debit ``` ### Authentication Requires [HMAC authentication](/api/v1/authentication) with headers: * `x-api-key`: Your API key ID only (e.g., `ak_live_xxx`) * `x-timestamp`: Current Unix timestamp in milliseconds * `x-signature`: HMAC-SHA256 signature (using derived signing key) ### Request Body | Field | Type | Required | Description | | ---------------------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `provider` | string | Yes | Payment provider: `BINANCE_PAY` | | `merchantContractCode` | string | No | Unique merchant contract code (alphanumeric, max 32 chars). Auto-generated if not provided | | `branchId` | string (UUID) | No | Branch ID to associate this contract with | | `serviceName` | string | Yes | Service name displayed to user (max 32 chars) | | `scenarioId` | string (UUID) | Yes | Scenario ID from scenario-code/list endpoint | | `currency` | string | Yes | Contract currency: `USDT` (crypto) or `LKR` (fiat) | | `singleUpperLimit` | number | Yes | Maximum amount per transaction in the specified currency (min 0.01) | | `slippageBps` | number | No | Slippage buffer in basis points for LKR contracts (0-20000 bps = 0-200%). Not allowed for USDT. Default: 0. Example: 10000 bps = 100% | | `webhookUrl` | string | No | Webhook URL for contract events | | `returnUrl` | string (URL) | Yes | URL to redirect user after successful contract signing (max 512 chars) | | `cancelUrl` | string (URL) | Yes | URL to redirect user after contract signing cancellation (max 512 chars) | ### Example Request (USDT Contract) ```bash theme={null} curl -X POST "https://api.ceypay.io/v1/direct-debit" \ -H "Content-Type: application/json" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" \ -d '{ "provider": "BINANCE_PAY", "currency": "USDT", "serviceName": "Monthly Subscription", "scenarioId": "550e8400-e29b-41d4-a716-446655440003", "singleUpperLimit": 100.0, "returnUrl": "https://your-app.com/contract/success", "cancelUrl": "https://your-app.com/contract/cancelled", "webhookUrl": "https://your-app.com/api/contract-webhook" }' ``` ### Example Request (LKR Contract with Slippage) ```bash theme={null} curl -X POST "https://api.ceypay.io/v1/direct-debit" \ -H "Content-Type: application/json" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" \ -d '{ "provider": "BINANCE_PAY", "currency": "LKR", "serviceName": "Monthly Subscription", "scenarioId": "550e8400-e29b-41d4-a716-446655440003", "singleUpperLimit": 33000.0, "slippageBps": 10000, "returnUrl": "https://your-app.com/contract/success", "cancelUrl": "https://your-app.com/contract/cancelled", "webhookUrl": "https://your-app.com/api/contract-webhook" }' ``` ### Example Response (USDT Contract) ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "merchantId": "merchant_12345", "merchantContractCode": "DD20260309120530A3F4", "serviceName": "Monthly Subscription", "status": "INITIATED", "currency": "USDT", "singleUpperLimit": 100.0, "paymentProvider": "BINANCE_PAY", "qrContent": "binance://contract?code=...", "deepLink": "https://app.binance.com/contract/sign?code=...", "createdAt": "2026-03-25T10:30:00.000Z" } ``` ### Example Response (LKR Contract) ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440001", "merchantId": "merchant_12345", "merchantContractCode": "DD20260309120530B4G5", "serviceName": "Monthly Subscription", "status": "INITIATED", "currency": "LKR", "singleUpperLimit": 200.0, "singleUpperLimitLkr": 33000.0, "slippageBps": 10000, "paymentProvider": "BINANCE_PAY", "qrContent": "binance://contract?code=...", "deepLink": "https://app.binance.com/contract/sign?code=...", "createdAt": "2026-03-25T10:30:00.000Z" } ``` **Note for LKR Contracts:** * `singleUpperLimit` shows the USDT amount sent to Binance (after currency conversion + slippage buffer) * `singleUpperLimitLkr` shows the original LKR amount specified * `slippageBps` shows the slippage buffer applied (10000 bps = 100%) * In this example: 33000 LKR ÷ 330 (exchange rate) = 100 USDT, with 100% slippage = 200 USDT total limit ### Error Responses | Status | Description | | ------ | ---------------------------------------------------------------------------------------------------- | | 400 | Bad request - invalid contract data, slippage not allowed for USDT, or missing exchange rate for LKR | | 401 | Unauthorized - invalid HMAC signature | | 403 | Forbidden - merchant not authorized | | 429 | Too many requests - rate limit exceeded | *** ## List Contracts Retrieves a paginated list of contracts for the authenticated merchant. Supports filtering by status, branch, currency, payment provider, and search. ``` GET /v1/direct-debit/list ``` ### Authentication Requires [HMAC authentication](/api/v1/authentication) with headers: * `x-api-key`: Your API key ID only (e.g., `ak_live_xxx`) * `x-timestamp`: Current Unix timestamp in milliseconds * `x-signature`: HMAC-SHA256 signature (using derived signing key) ### Query Parameters | Parameter | Type | Required | Description | | ----------------- | ------------- | -------- | ---------------------------------------------------------------- | | `page` | number | No | Page number (default: 1) | | `limit` | number | No | Items per page (default: 20, max: 100) | | `status` | string | No | Filter by status: `INITIATED`, `SIGNED`, `TERMINATED`, `EXPIRED` | | `branchId` | string (UUID) | No | Filter by branch ID | | `currency` | string | No | Filter by currency: `USDT`, `LKR` | | `paymentProvider` | string | No | Filter by provider: `BINANCE_PAY`, `BYBIT_PAY`, `KUCOIN_PAY` | | `search` | string | No | Search by merchant contract code or service name | ### Example Request ```bash theme={null} curl "https://api.ceypay.io/v1/direct-debit/list?page=1&limit=20&status=SIGNED" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" ``` ### Example Response ```json theme={null} { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "merchantContractCode": "DD20260309120530A3F4", "serviceName": "Monthly Subscription", "status": "SIGNED", "currency": "USDT", "singleUpperLimit": 100.0, "paymentProvider": "BINANCE_PAY", "createdAt": "2026-03-25T10:30:00.000Z" } ], "meta": { "page": 1, "limit": 20, "totalItems": 45, "totalPages": 3 } } ``` ### Error Responses | Status | Description | | ------ | --------------------------------------- | | 401 | Unauthorized - invalid HMAC signature | | 429 | Too many requests - rate limit exceeded | *** ## Get Contract Details Retrieves contract details by ID. Only returns contracts belonging to the authenticated merchant. ``` GET /v1/direct-debit/:id ``` ### Authentication Requires [HMAC authentication](/api/v1/authentication) with headers: * `x-api-key`: Your API key ID only (e.g., `ak_live_xxx`) * `x-timestamp`: Current Unix timestamp in milliseconds * `x-signature`: HMAC-SHA256 signature (using derived signing key) ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------------- | -------- | ----------- | | `id` | string (UUID) | Yes | Contract ID | ### Example Request ```bash theme={null} curl "https://api.ceypay.io/v1/direct-debit/550e8400-e29b-41d4-a716-446655440000" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" ``` ### Example Response ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "merchantId": "merchant_12345", "branchId": "550e8400-e29b-41d4-a716-446655440002", "paymentProvider": "BINANCE_PAY", "merchantContractCode": "DD20260309120530A3F4", "preContractId": "29383937493038367292", "contractId": 123456789, "bizId": "420471959722147840", "serviceName": "Monthly Subscription", "scenarioId": "550e8400-e29b-41d4-a716-446655440003", "currency": "USDT", "singleUpperLimit": 100.0, "periodic": false, "contractEndTime": "2027-03-25T00:00:00.000Z", "requestExpireTime": "2026-03-26T00:00:00.000Z", "openUserId": "eb6b287a44dd73dd81645a3cbcfee162", "merchantAccountNo": "user@example.com", "status": "SIGNED", "paymentCount": 3, "totalAmountCharged": 150.0, "lastPaymentAt": "2026-03-24T10:30:00.000Z", "webhookUrl": "https://your-app.com/api/contract-webhook", "createdAt": "2026-03-25T10:30:00.000Z", "updatedAt": "2026-03-25T11:00:00.000Z" } ``` ### Error Responses | Status | Description | | ------ | ------------------------------------------------ | | 401 | Unauthorized - invalid HMAC signature | | 403 | Forbidden - contract does not belong to merchant | | 404 | Contract not found | | 429 | Too many requests - rate limit exceeded | *** ## Query/Sync Contract Status Queries the payment provider for the latest contract status and syncs it to the database. Use this to check if a user has signed the contract. ``` POST /v1/direct-debit/:id/sync ``` ### Authentication Requires [HMAC authentication](/api/v1/authentication) with headers: * `x-api-key`: Your API key ID only (e.g., `ak_live_xxx`) * `x-timestamp`: Current Unix timestamp in milliseconds * `x-signature`: HMAC-SHA256 signature (using derived signing key) ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------------- | -------- | ----------- | | `id` | string (UUID) | Yes | Contract ID | ### Request Body Empty object `{}` ### Example Request ```bash theme={null} curl -X POST "https://api.ceypay.io/v1/direct-debit/550e8400-e29b-41d4-a716-446655440000/sync" \ -H "Content-Type: application/json" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" \ -d '{}' ``` ### Example Response ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "merchantId": "merchant_12345", "paymentProvider": "BINANCE_PAY", "merchantContractCode": "DD20260309120530A3F4", "preContractId": "29383937493038367292", "contractId": 123456789, "bizId": "420471959722147840", "serviceName": "Monthly Subscription", "scenarioId": "550e8400-e29b-41d4-a716-446655440003", "currency": "USDT", "singleUpperLimit": 100.0, "periodic": false, "contractEndTime": "2027-03-25T00:00:00.000Z", "requestExpireTime": "2026-03-26T00:00:00.000Z", "openUserId": "eb6b287a44dd73dd81645a3cbcfee162", "merchantAccountNo": "user@example.com", "status": "SIGNED", "paymentCount": 0, "totalAmountCharged": 0, "webhookUrl": "https://your-app.com/api/contract-webhook", "createdAt": "2026-03-25T10:30:00.000Z", "updatedAt": "2026-03-25T11:00:00.000Z" } ``` ### Error Responses | Status | Description | | ------ | --------------------------------------- | | 401 | Unauthorized - invalid HMAC signature | | 404 | Contract not found | | 429 | Too many requests - rate limit exceeded | *** ## Terminate Contract Terminates a signed Direct Debit contract. Only contracts in `SIGNED` status can be terminated. ``` POST /v1/direct-debit/:id/terminate ``` ### Authentication Requires [HMAC authentication](/api/v1/authentication) with headers: * `x-api-key`: Your API key ID only (e.g., `ak_live_xxx`) * `x-timestamp`: Current Unix timestamp in milliseconds * `x-signature`: HMAC-SHA256 signature (using derived signing key) ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------------- | -------- | ----------- | | `id` | string (UUID) | Yes | Contract ID | ### Request Body | Field | Type | Required | Description | | ------------------ | ------ | -------- | --------------------------------------------------------- | | `terminationNotes` | string | No | Optional notes about contract termination (max 256 chars) | ### Example Request ```bash theme={null} curl -X POST "https://api.ceypay.io/v1/direct-debit/550e8400-e29b-41d4-a716-446655440000/terminate" \ -H "Content-Type: application/json" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" \ -d '{ "terminationNotes": "User requested cancellation" }' ``` ### Example Response ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "merchantId": "merchant_12345", "paymentProvider": "BINANCE_PAY", "merchantContractCode": "DD20260309120530A3F4", "preContractId": "29383937493038367292", "contractId": 123456789, "bizId": "420471959722147840", "serviceName": "Monthly Subscription", "scenarioId": "550e8400-e29b-41d4-a716-446655440003", "currency": "USDT", "singleUpperLimit": 100.0, "periodic": false, "contractEndTime": "2027-03-25T00:00:00.000Z", "requestExpireTime": "2026-03-26T00:00:00.000Z", "openUserId": "eb6b287a44dd73dd81645a3cbcfee162", "merchantAccountNo": "user@example.com", "status": "TERMINATED", "contractTerminationWay": 3, "contractTerminationTime": "2026-03-25T12:00:00.000Z", "terminationNotes": "User requested cancellation", "paymentCount": 5, "totalAmountCharged": 250.0, "lastPaymentAt": "2026-03-24T10:30:00.000Z", "webhookUrl": "https://your-app.com/api/contract-webhook", "createdAt": "2026-03-25T10:30:00.000Z", "updatedAt": "2026-03-25T12:00:00.000Z" } ``` ### Error Responses | Status | Description | | ------ | ------------------------------------------- | | 400 | Bad request - contract not in SIGNED status | | 401 | Unauthorized - invalid HMAC signature | | 404 | Contract not found | | 429 | Too many requests - rate limit exceeded | *** ## Execute Payment Executes an on-demand payment against a signed Direct Debit contract. Amount must not exceed the contract's single upper limit. ``` POST /v1/direct-debit/:id/payment ``` ### Authentication Requires [HMAC authentication](/api/v1/authentication) with headers: * `x-api-key`: Your API key ID only (e.g., `ak_live_xxx`) * `x-timestamp`: Current Unix timestamp in milliseconds * `x-signature`: HMAC-SHA256 signature (using derived signing key) ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------------- | -------- | ----------- | | `id` | string (UUID) | Yes | Contract ID | ### Request Body | Field | Type | Required | Description | | ----------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------- | | `currency` | string | Yes | Payment currency: `USDT` or `LKR` (can differ from contract currency) | | `amount` | number | Yes | Payment amount in the specified currency (min 0.01). Must not exceed contract's buffered limit after conversion | | `productName` | string | Yes | Product name (max 256 chars) | | `productDetail` | string | No | Product detail (max 256 chars) | | `goods` | array | No | Array of goods items (see structure below) | | `webhookUrl` | string | No | Webhook URL to override contract webhook | | `customerBilling` | object | No | Customer billing information (see structure below) | #### Goods Item Structure | Field | Type | Required | Description | | ------------------ | ------ | -------- | -------------------------------------------------- | | `goodsType` | string | Yes | Goods type (01: Tangible goods, 02: Virtual goods) | | `goodsCategory` | string | Yes | Goods category code (e.g., Z000) | | `referenceGoodsId` | string | Yes | Reference goods ID | | `goodsName` | string | Yes | Goods name (max 256 chars) | | `goodsDetail` | string | No | Goods detail description (max 256 chars) | #### Customer Billing Structure | Field | Type | Required | Description | | ----------- | ------ | -------- | ------------------- | | `firstName` | string | Yes | Customer first name | | `lastName` | string | Yes | Customer last name | | `email` | string | Yes | Customer email | | `phone` | string | No | Customer phone | | `address` | string | No | Customer address | ### Example Request (USDT Payment) ```bash theme={null} curl -X POST "https://api.ceypay.io/v1/direct-debit/550e8400-e29b-41d4-a716-446655440000/payment" \ -H "Content-Type: application/json" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" \ -d '{ "currency": "USDT", "amount": 50.0, "productName": "Monthly Subscription", "productDetail": "Premium membership for March 2026", "customerBilling": { "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+94771234567" }, "webhookUrl": "https://your-app.com/api/payment-webhook" }' ``` ### Example Request (LKR Payment) ```bash theme={null} curl -X POST "https://api.ceypay.io/v1/direct-debit/550e8400-e29b-41d4-a716-446655440001/payment" \ -H "Content-Type: application/json" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" \ -d '{ "currency": "LKR", "amount": 16500.0, "productName": "Monthly Subscription", "productDetail": "Premium membership for March 2026", "customerBilling": { "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+94771234567" }, "webhookUrl": "https://your-app.com/api/payment-webhook" }' ``` ### Example Response ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440001", "merchantId": "merchant_12345", "payId": "binance_pay_xyz789", "paymentNo": "320045925617", "amount": 50.0, "currency": "USDT", "status": "INITIATED", "paymentProvider": "BINANCE_PAY", "directDebitContractId": "550e8400-e29b-41d4-a716-446655440000", "createdAt": "2026-03-25T10:30:00.000Z", "feeBreakdown": { "grossAmountUSDT": 50.0, "exchangeFeePercentage": 1.0, "exchangeFeeAmountUSDT": 0.50, "ceypayFeePercentage": 0.5, "ceypayFeeAmountUSDT": 0.25, "totalFeesUSDT": 0.75, "netAmountUSDT": 49.25 } } ``` ### Response Notes * Direct debit payments do not include `qrContent` or `checkoutLink` fields as the payment is charged directly without user interaction * Payment starts in `INITIATED` status and transitions to `PAID` via webhook notification * In sandbox mode, payments are automatically marked as `PAID` ### Error Responses | Status | Description | | ------ | ------------------------------------------------------------------------------------------------------------------------ | | 400 | Bad request - contract not signed, expired, amount exceeds limit after currency conversion, or exchange rate unavailable | | 401 | Unauthorized - invalid HMAC signature | | 404 | Contract not found | | 429 | Too many requests - rate limit exceeded | *** ## Currency & Slippage ### Currency Support Direct Debit contracts support two currencies: | Currency | Type | Description | | -------- | ------ | ------------------------------- | | `USDT` | Crypto | Tether stablecoin on blockchain | | `LKR` | Fiat | Sri Lankan Rupee | ### Cross-Currency Payments You can execute payments in either USDT or LKR regardless of the contract's currency: * **USDT contract** → Can accept both USDT and LKR payments * **LKR contract** → Can accept both LKR and USDT payments All payments are converted to USDT before being sent to the payment provider. ### Slippage Protection for LKR Contracts When creating an LKR contract, exchange rates may fluctuate between contract creation and payment execution. Slippage protection adds a buffer to handle these fluctuations. **Example:** ``` Contract: 1000 LKR with 100% slippage (10000 bps) Exchange rate: 1 USDT = 330 LKR Calculation: 1. Base conversion: 1000 LKR ÷ 330 = 3.03 USDT 2. Apply slippage: 3.03 USDT × (1 + 100%) = 6.06 USDT 3. Contract limit sent to Binance: 6.06 USDT This allows: - LKR payments up to ~2000 LKR (if rate stays at 330) - USDT payments up to 6.06 USDT ``` **Slippage in Basis Points:** * 0 bps = 0% (no buffer) * 5000 bps = 50% * 10000 bps = 100% * 20000 bps = 200% (maximum) **Important Notes:** * Slippage only applies to LKR contracts * USDT contracts do not use slippage (rejected if provided) * Slippage defaults to 0 if not specified for LKR contracts * Maximum allowed slippage is 20000 bps (200%) ### Payment Validation When executing a payment: 1. **Currency conversion**: Payment amount is converted to USDT using current exchange rate 2. **Limit validation**: Converted USDT amount must not exceed contract's `singleUpperLimit` (which includes slippage buffer for LKR contracts) 3. **Error handling**: If payment exceeds limit, request is rejected with clear error message showing the conversion **Example Error Response:** ```json theme={null} { "statusCode": 400, "message": "Payment 5000 LKR converts to 15.15 USDT, exceeding contract limit 6.06 USDT.", "error": "Bad Request" } ``` *** ## Contract Status Reference Direct Debit contracts have the following lifecycle statuses: | Status | Description | | ------------ | ------------------------------------------------- | | `INITIATED` | Contract created but not yet signed by user | | `SIGNED` | User has signed the contract (ready for payments) | | `TERMINATED` | Contract has been terminated | | `EXPIRED` | Contract has expired | *** ## Webhooks Set `webhookUrl` in your contract creation request to receive real-time contract and payment status updates. See [Webhooks Guide](/api/v1/webhooks) for payload format, signature verification, and retry policy. *** ## Error Responses | Status | Error | Description | | ------ | ----------------- | ---------------------------------------- | | 400 | Bad Request | Invalid request body or validation error | | 401 | Unauthorized | Invalid API key or signature | | 403 | Forbidden | Contract belongs to another merchant | | 404 | Not Found | Contract not found | | 429 | Too Many Requests | Rate limit exceeded | See [Error Codes](/api/v1/errors) for detailed error handling. *** ## Related Documentation * [Quick Start](/api/v1/quickstart) - Getting started guide * [Authentication](/api/v1/authentication) - HMAC signature details * [Webhooks](/api/v1/webhooks) - Receive payment notifications * [Rate Limits](/api/v1/rate-limits) - API rate limiting * [Errors](/api/v1/errors) - Error codes reference # Error Codes Reference Source: https://docs.ceypay.io/api/v1/errors Complete guide to all error responses you may encounter when using the CeyPay API, with troubleshooting steps for each. ## Error Response Format All API errors follow this consistent format: ```json theme={null} { "statusCode": 400, "message": "Validation failed", "error": "Bad Request", "details": [ { "field": "amount", "constraint": "min", "message": "amount must be greater than 0" } ] } ``` ## HTTP Status Codes ### 2xx Success #### 200 OK Request succeeded (GET, PATCH, DELETE operations). #### 201 Created Resource created successfully (POST operations). ### 4xx Client Errors #### 400 Bad Request **Causes:** * Invalid request body * Validation errors * Unsupported payment provider * Invalid currency **Example:** ```json theme={null} { "statusCode": 400, "message": "Validation failed", "error": "Bad Request", "details": [ { "field": "currency", "constraint": "isEnum", "message": "currency must be one of: USDT, LKR" } ] } ``` **Fix:** * Check request body matches the API specification * Ensure all required fields are provided * Validate field types and formats * Use supported currencies (USDT, LKR) *** #### 401 Unauthorized **Causes:** * Invalid API key * Expired or revoked API key * Invalid HMAC signature * Timestamp outside valid window (±5 minutes) * Missing authentication headers **Example - Invalid Signature:** ```json theme={null} { "statusCode": 401, "message": "Invalid signature", "error": "Unauthorized" } ``` **Fix:** 1. Verify API key is correct and active 2. Check signature calculation: ```javascript theme={null} const message = timestamp + method + path + body; const signature = crypto.createHmac('sha256', secretKey) .update(message) .digest('hex'); ``` 3. Ensure timestamp is current (within 5 minutes) 4. Verify method is uppercase (GET, POST, PATCH, DELETE) 5. Include query parameters in path if any **Example - Timestamp Error:** ```json theme={null} { "statusCode": 401, "message": "Request timestamp outside valid window", "error": "Unauthorized" } ``` **Fix:** * Use current timestamp: `Date.now().toString()` * Check server clock is synchronized (use NTP) * Don't reuse old timestamps *** #### 403 Forbidden **Causes:** * Accessing resources belonging to another merchant * Insufficient permissions * Branch access denied **Example:** ```json theme={null} { "statusCode": 403, "message": "Payment does not belong to your merchant account", "error": "Forbidden" } ``` **Fix:** * Ensure you're requesting your own resources * Check merchantId in request matches your account * Verify branch access if using branch-specific endpoints *** #### 404 Not Found **Causes:** * Resource doesn't exist * Invalid resource ID * Typo in endpoint URL **Example:** ```json theme={null} { "statusCode": 404, "message": "Payment with ID 550e8400-... not found", "error": "Not Found" } ``` **Fix:** * Verify resource ID is correct * Check resource hasn't been deleted * Ensure endpoint URL is correct *** #### 409 Conflict **Causes:** * Duplicate resource * Resource state conflict * Payment link slug already exists **Example:** ```json theme={null} { "statusCode": 409, "message": "Payment link with this slug already exists", "error": "Conflict" } ``` **Fix:** * Use a different slug/identifier * Check if resource already exists * Handle conflict in your application logic *** #### 410 Gone **Causes:** * Payment link expired * Single-use payment link already consumed * Resource permanently deleted **Example:** ```json theme={null} { "statusCode": 410, "message": "Payment link has expired", "error": "Gone" } ``` **Fix:** * Create a new payment link * Check expiration dates before use * Don't reuse single-use payment links *** #### 422 Unprocessable Entity **Causes:** * Business logic validation failed * Invalid amount (negative, zero, or too large) * Custom amount not allowed * Amount outside min/max range **Example:** ```json theme={null} { "statusCode": 422, "message": "Amount must be between 5 and 10000 USDT", "error": "Unprocessable Entity" } ``` **Fix:** * Review business validation rules * Check amount constraints * Verify custom amount settings *** #### 429 Too Many Requests **Causes:** * Rate limit exceeded for endpoint * Too many requests in time window **Example:** ```json theme={null} { "statusCode": 429, "message": "Rate limit exceeded. Maximum 100 requests per minute. Try again in 45 seconds.", "error": "Too Many Requests", "retryAfter": 45 } ``` **Headers:** ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1640000000 Retry-After: 45 ``` **Fix:** * Wait for the time specified in `retryAfter` * Implement exponential backoff * Cache responses where appropriate * Review [rate-limits.md](/api/v1/rate-limits) for limits per endpoint *** ### 5xx Server Errors #### 500 Internal Server Error **Causes:** * Unexpected server error * Database connection issue * Service temporarily unavailable **Example:** ```json theme={null} { "statusCode": 500, "message": "An unexpected error occurred", "error": "Internal Server Error" } ``` **Fix:** * Retry the request with exponential backoff * Check [status.ceypay.io](https://status.ceypay.io) for service status * Contact support if error persists *** #### 502 Bad Gateway **Causes:** * Payment provider (Bybit) API failure * Upstream service unavailable * Network timeout **Example:** ```json theme={null} { "statusCode": 502, "message": "Failed to create payment with provider", "error": "Bad Gateway" } ``` **Fix:** * Retry the request after a short delay * Check payment provider status * Contact support if issue persists *** ## Common Error Scenarios ### Validation Errors ```json theme={null} { "statusCode": 400, "message": "Validation failed", "error": "Bad Request", "details": [ { "field": "amount", "constraint": "min", "message": "amount must be greater than 0" }, { "field": "goods", "constraint": "arrayNotEmpty", "message": "goods must not be empty" }, { "field": "customerBilling.email", "constraint": "isEmail", "message": "customerBilling.email must be a valid email" } ] } ``` **How to handle:** ```javascript theme={null} try { const payment = await createPayment(data); } catch (error) { if (error.response?.status === 400 && error.response?.data?.details) { // Display validation errors to user error.response.data.details.forEach(err => { console.error(`${err.field}: ${err.message}`); }); } } ``` ### Authentication Errors ```javascript theme={null} // Example: Debugging signature issues const timestamp = Date.now().toString(); const method = 'POST'; const path = '/v1/payments'; const body = JSON.stringify(paymentData); console.log('=== Debugging Signature ==='); console.log('Timestamp:', timestamp); console.log('Method:', method); console.log('Path:', path); console.log('Body:', body); const message = timestamp + method + path + body; console.log('Message to sign:', message); const signature = crypto .createHmac('sha256', secretKey) .update(message) .digest('hex'); console.log('Generated signature:', signature); try { const response = await axios.post(url, JSON.parse(body), { headers: { 'x-api-key': apiKey, 'x-timestamp': timestamp, 'x-signature': signature } }); } catch (error) { if (error.response?.status === 401) { console.error('Authentication failed:', error.response.data); console.error('Check:'); console.error('1. API key is correct and active'); console.error('2. Timestamp is current (within 5 minutes)'); console.error('3. Signature calculation matches exactly'); console.error('4. Method is uppercase'); } } ``` ### Rate Limit Handling ```javascript theme={null} async function makeApiRequest(url, options, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await axios(url, options); return response.data; } catch (error) { if (error.response?.status === 429) { const retryAfter = error.response.data.retryAfter || 60; console.log(`Rate limited. Retrying in ${retryAfter} seconds...`); await sleep(retryAfter * 1000); continue; } throw error; } } throw new Error('Max retries exceeded'); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } ``` ### Network Error Handling ```javascript theme={null} async function createPaymentWithRetry(data) { const maxRetries = 3; const retryDelay = 1000; // Start with 1 second for (let i = 0; i < maxRetries; i++) { try { return await createPayment(data); } catch (error) { // Retry on network errors and 5xx errors const shouldRetry = !error.response || (error.response.status >= 500 && error.response.status < 600); if (shouldRetry && i < maxRetries - 1) { const delay = retryDelay * Math.pow(2, i); // Exponential backoff console.log(`Request failed. Retrying in ${delay}ms...`); await sleep(delay); continue; } throw error; } } } ``` ## Error Handling Best Practices ### 1. Always Handle Errors ```javascript theme={null} try { const payment = await createPayment(data); console.log('Payment created:', payment.id); } catch (error) { console.error('Failed to create payment:', error.message); // Log for debugging if (error.response) { console.error('Status:', error.response.status); console.error('Error:', error.response.data); } // Show user-friendly message showErrorToUser('Failed to process payment. Please try again.'); } ``` ### 2. Implement Retry Logic ```javascript theme={null} const RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 503, 504]; function shouldRetry(error) { return ( !error.response || RETRYABLE_STATUS_CODES.includes(error.response.status) ); } ``` ### 3. Log Errors with Context ```javascript theme={null} function logError(error, context) { console.error({ timestamp: new Date().toISOString(), context, status: error.response?.status, message: error.response?.data?.message || error.message, details: error.response?.data?.details, stack: error.stack }); } try { await createPayment(data); } catch (error) { logError(error, { operation: 'createPayment', amount: data.amount, currency: data.currency }); } ``` ### 4. Provide User-Friendly Messages ```javascript theme={null} function getErrorMessage(error) { const status = error.response?.status; switch (status) { case 400: return 'Invalid payment information. Please check your details.'; case 401: return 'Authentication failed. Please contact support.'; case 403: return 'You don\'t have permission to perform this action.'; case 404: return 'Payment not found. It may have been deleted.'; case 429: return 'Too many requests. Please wait a moment and try again.'; case 500: case 502: case 503: return 'Our service is temporarily unavailable. Please try again in a few minutes.'; default: return 'An unexpected error occurred. Please try again.'; } } ``` ### 5. Monitor Error Rates ```javascript theme={null} const errorStats = { total: 0, byStatus: {}, byEndpoint: {} }; function trackError(error, endpoint) { errorStats.total++; const status = error.response?.status || 'network'; errorStats.byStatus[status] = (errorStats.byStatus[status] || 0) + 1; errorStats.byEndpoint[endpoint] = (errorStats.byEndpoint[endpoint] || 0) + 1; // Alert if error rate is high if (errorStats.total > 100) { console.warn('High error rate detected:', errorStats); } } ``` ## Getting Help If you encounter persistent errors: 1. **Check System Status**: [status.ceypay.io](https://status.ceypay.io) 2. **Review Documentation**: Ensure you're following the API specification 3. **Enable Debug Logging**: Log requests, responses, and signatures 4. **Contact Support**: [support@ceypay.io](mailto:support@ceypay.io) with: * Error message and status code * Request details (endpoint, timestamp, signature) * Steps to reproduce * Logs (remove sensitive data!) ## Related Documentation * [Authentication Guide](/api/v1/authentication) - HMAC signature implementation * [Webhooks Guide](/api/v1/webhooks) - Webhook integration and errors * [Rate Limits](/api/v1/rate-limits) - API rate limiting policies * [Quick Start](/api/v1/quickstart) - Getting started guide # Payment Links API Source: https://docs.ceypay.io/api/v1/payment-links Accept payments without full checkout integration. Generate reusable or single-use payment links and share them via email, SMS, or social media. ## Endpoints | Method | Endpoint | Description | Rate Limit | | ------ | ----------------------- | ------------------------- | ----------- | | POST | `/v1/payment-link` | Create a new payment link | 50 req/min | | GET | `/v1/payment-link/:id` | Get payment link details | 100 req/min | | GET | `/v1/payment-link/list` | List payment links | 100 req/min | | DELETE | `/v1/payment-link/:id` | Deactivate a payment link | 50 req/min | *** ## Create Payment Link Create a new payment link. ``` POST /v1/payment-link ``` ### Example Request ```bash theme={null} curl -X POST "https://api.ceypay.io/v1/payment-link" \ -H "Content-Type: application/json" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" \ -d '{ "merchantTradeNo": "ORDER-2025-001", "name": "Premium Subscription", "description": "Monthly premium plan subscription", "amount": 49.99, "currency": "USDT", "reusable": false, "allowCustomAmount": false, "allowQuantityBuy": true, "maxQuantity": 10, "successUrl": "https://yourstore.com/payment/success", "cancelUrl": "https://yourstore.com/payment/cancelled", "webhookUrl": "https://yourstore.com/webhooks/ceypay" }' ``` ### Request Body | Field | Type | Required | Description | | ------------------- | ------- | -------- | ------------------------------------------------------------------------------ | | `name` | string | Yes | Name of the payment link. | | `description` | string | No | Description of the payment. | | `amount` | number | No\* | Fixed amount. Required if `allowCustomAmount` is false. | | `currency` | string | Yes | Currency code (e.g., `USDT`, `LKR`). | | `allowCustomAmount` | boolean | Yes | Whether to allow customers to enter a custom amount. Default: `false`. | | `minAmount` | number | No\* | Minimum amount for custom payments. Required if `allowCustomAmount` is true. | | `maxAmount` | number | No\* | Maximum amount for custom payments. Required if `allowCustomAmount` is true. | | `allowQuantityBuy` | boolean | No | Allow customers to purchase multiple quantities. Default: `false`. | | `maxQuantity` | number | No\* | Maximum quantity allowed per purchase. Required if `allowQuantityBuy` is true. | | `reusable` | boolean | No | Whether the link can be used multiple times. Default: `true`. | | `expirationDate` | string | No | Expiration date in ISO 8601 format. | | `successUrl` | string | No | URL to redirect customers after successful payment. | | `cancelUrl` | string | No | URL to redirect customers if they cancel the payment. | | `webhookUrl` | string | No | Webhook URL to receive payment notifications. | | `merchantTradeNo` | string | No | Unique merchant trade number for tracking. | | `branchId` | string | No | Branch ID to associate with. | ### Example Response (201 Created) ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "merchant_id": "merchant_12345", "slug": "link_abc123xyz", "merchantTradeNo": "ORDER-2025-001", "name": "Premium Subscription", "description": "Monthly premium plan subscription", "amount": 49.99, "currency": "USDT", "allowCustomAmount": false, "allowQuantityBuy": true, "maxQuantity": 10, "reusable": false, "successUrl": "https://yourstore.com/payment/success", "cancelUrl": "https://yourstore.com/payment/cancelled", "webhookUrl": "https://yourstore.com/webhooks/ceypay", "used_count": 0, "status": "ACTIVE", "creator_type": "API_KEY", "creator_id": "ak_live_abc123", "created_at": "2025-11-25T10:30:00Z", "updated_at": "2025-11-25T10:30:00Z" } ``` *** ## Get Payment Link Get details of a specific payment link. ``` GET /v1/payment-link/:id ``` ### Example Request ```bash theme={null} curl "https://api.ceypay.io/v1/payment-link/550e8400-e29b-41d4-a716-446655440000" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" ``` ### Example Response (200 OK) ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "merchant_id": "merchant_12345", "slug": "link_abc123xyz", "name": "Premium Subscription", "description": "Monthly premium plan subscription", "amount": 49.99, "currency": "USDT", "status": "ACTIVE", ... } ``` *** ## List Payment Links Retrieve a paginated list of payment links. ``` GET /v1/payment-link/list ``` ### Example Request ```bash theme={null} curl "https://api.ceypay.io/v1/payment-link/list?page=1&pageSize=20" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" ``` ### Query Parameters | Parameter | Type | Description | | ---------- | ------ | ------------------------------ | | `page` | number | Page number (default: 1). | | `pageSize` | number | Items per page (default: 20). | | `branchId` | string | Filter by branch ID. | | `search` | string | Search by name or description. | ### Example Response (200 OK) ```json theme={null} { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Premium Subscription", "amount": 49.99, "currency": "USDT", "status": "ACTIVE", ... } ], "page": 1, "limit": 20, "total": 45, "totalPages": 3, "hasNext": true, "hasPrev": false } ``` *** ## Deactivate Payment Link Deactivate (soft delete) a payment link. It will no longer accept new payments. ``` DELETE /v1/payment-link/:id ``` ### Example Request ```bash theme={null} curl -X DELETE "https://api.ceypay.io/v1/payment-link/550e8400-e29b-41d4-a716-446655440000" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" ``` ### Example Response (200 OK) ```json theme={null} { "message": "Payment link deactivated successfully" } ``` *** ## Error Codes | Status | Description | | ------ | ---------------------------------------- | | 400 | Bad Request - Invalid data. | | 401 | Unauthorized - Invalid signature. | | 403 | Forbidden - Access denied. | | 404 | Not Found - Payment link not found. | | 429 | Too Many Requests - Rate limit exceeded. | # Payments API Source: https://docs.ceypay.io/api/v1/payments Create and manage crypto payments through CeyPay. Accept USDT payments from customers with real-time processing. ## Endpoints | Method | Endpoint | Description | Rate Limit | | ------ | ------------------ | -------------------- | ----------- | | POST | `/v1/payment` | Create a new payment | 100 req/min | | GET | `/v1/payment/:id` | Get payment details | 300 req/min | | GET | `/v1/payment/list` | List payments | 200 req/min | *** ## Create Payment Creates a new payment order. Returns a QR code and checkout link for customer payment. ``` POST /v1/payment ``` ### Authentication Requires [HMAC authentication](/api/v1/authentication) with headers: * `x-api-key`: Your API key ID only (e.g., `ak_live_xxx`) * `x-timestamp`: Current Unix timestamp in milliseconds * `x-signature`: HMAC-SHA256 signature (using derived signing key) ### Request Body | Field | Type | Required | Description | | ---------------------------- | ------------- | -------- | ------------------------------------------- | | `amount` | number | Yes | Payment amount (minimum 0) | | `currency` | string | Yes | Currency code: `USDT` or `LKR` | | `provider` | string | Yes | Payment provider: `BYBIT` or `BINANCE` | | `goods` | array | Yes | Array of goods/products being purchased | | `goods[].name` | string | Yes | Product name | | `goods[].description` | string | Yes | Product description | | `goods[].mccCode` | string | No | Merchant Category Code (default: `5818`) | | `branchId` | string (UUID) | No | Branch ID for multi-branch merchants | | `orderExpireTime` | string | No | Expiration time (Unix timestamp in seconds) | | `webhookUrl` | string | No | URL to receive payment status updates | | `merchantTradeNo` | string | No | Your internal order reference | | `customerBilling` | object | Yes | Customer billing details | | `customerBilling.firstName` | string | Yes | Customer first name | | `customerBilling.lastName` | string | Yes | Customer last name | | `customerBilling.email` | string | Yes | Customer email | | `customerBilling.phone` | string | Yes | Customer phone | | `customerBilling.address` | string | Yes | Street address | | `customerBilling.city` | string | No | City | | `customerBilling.postalCode` | string | No | Postal/ZIP code | | `customerBilling.country` | string | No | Country | ### Currency Handling * **USDT**: Payment is created directly in USDT * **LKR**: Amount is automatically converted to USDT using the current exchange rate ### Example Request ```bash theme={null} curl -X POST "https://api.ceypay.io/v1/payment" \ -H "Content-Type: application/json" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" \ -d '{ "amount": 149.99, "currency": "USDT", "provider": "BYBIT", "goods": [ { "name": "Wireless Headphones", "description": "Noise-cancelling Bluetooth headphones", "mccCode": "5732" } ], "webhookUrl": "https://mystore.com/api/payment-webhook", "customerBilling": { "email": "customer@example.com", "firstName": "John", "lastName": "Doe", "phone": "+94771234567", "address": "123 Main Street" } }' ``` ### Example Response ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "merchantId": "merchant_12345", "payId": "bybit_pay_abc123", "paymentNo": "320045925616", "amount": 149.99, "currency": "USDT", "status": "INITIATED", "qrContent": "bybit://pay?id=abc123...", "checkoutLink": "https://checkout.bybit.com/pay/abc123", "goods": [ { "name": "Wireless Headphones", "description": "Noise-cancelling Bluetooth headphones", "mccCode": "5732" } ], "customerBilling": { "email": "customer@example.com", "firstName": "John", "lastName": "Doe" }, "paymentProvider": "BYBIT", "expireTime": "2025-01-15T11:30:00.000Z", "createdAt": "2025-01-15T10:30:00.000Z", "feeBreakdown": { "grossAmountUSDT": 149.99, "exchangeFeePercentage": 1.0, "exchangeFeeAmountUSDT": 1.5, "ceypayFeePercentage": 0.5, "ceypayFeeAmountUSDT": 0.75, "totalFeesUSDT": 2.25, "netAmountUSDT": 147.74 } } ``` ### Response Fields | Field | Type | Description | | ----------------- | ------ | -------------------------------------------------------- | | `id` | string | Unique payment ID | | `merchantId` | string | Your merchant ID | | `payId` | string | Payment provider's payment ID | | `paymentNo` | string | Merchant trade number | | `amount` | number | Payment amount in USDT | | `currency` | string | Currency code (always USDT for processing) | | `status` | string | Payment status: `INITIATED`, `PAID`, `EXPIRED`, `FAILED` | | `qrContent` | string | QR code content for mobile wallet scanning | | `checkoutLink` | string | Web checkout URL to redirect customers | | `goods` | array | Products/services in this payment | | `customerBilling` | object | Customer billing details (if provided) | | `paymentProvider` | string | Payment provider used | | `expireTime` | string | When the payment expires (ISO 8601) | | `createdAt` | string | When the payment was created (ISO 8601) | | `feeBreakdown` | object | Fee breakdown details | ### LKR Payment Response When creating a payment in LKR, additional fields are returned: ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "amount": 101.52, "currency": "USDT", "usdtAmount": 101.52, "exchangeRateSnapshot": 295.5, "lkrGrossAmount": 30000.0, "lkrExchangeFeeAmount": 750.0, "lkrCeypayFeeAmount": 300.0, "lkrNetAmount": 28950.0, "feeBreakdown": { "grossAmountUSDT": 101.52, "grossAmountLKR": 30000.0, "exchangeFeePercentage": 2.5, "exchangeFeeAmountUSDT": 2.54, "exchangeFeeAmountLKR": 750.0, "ceypayFeePercentage": 1.0, "ceypayFeeAmountUSDT": 1.02, "ceypayFeeAmountLKR": 300.0, "totalFeesUSDT": 3.56, "totalFeesLKR": 1050.0, "netAmountUSDT": 97.96, "netAmountLKR": 28950.0 } } ``` *** ## Get Payment Retrieve details for a specific payment. ``` GET /v1/payment/:id ``` ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ----------- | | `id` | string | Payment ID | ### Example Request ```bash theme={null} curl "https://api.ceypay.io/v1/payment/550e8400-e29b-41d4-a716-446655440000" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" ``` ### Error Responses | Status | Description | | ------ | ------------------------------------------------ | | 403 | Payment does not belong to your merchant account | | 404 | Payment not found | *** ## List Payments Retrieve a paginated list of your payments. ``` GET /v1/payment/list ``` ### Query Parameters | Parameter | Type | Default | Description | | ----------- | ------ | ------- | ---------------------------------------------------------- | | `page` | number | 1 | Page number | | `pageSize` | number | 20 | Items per page | | `status` | string | - | Filter by status: `INITIATED`, `PAID`, `EXPIRED`, `FAILED` | | `dateRange` | string | - | Date range preset: `last7`, `last30`, `last90`, `custom` | | `search` | string | - | Search by payment ID or customer info | ### Example Request ```bash theme={null} curl "https://api.ceypay.io/v1/payment/list?page=1&pageSize=20&status=PAID" \ -H "x-api-key: ak_live_abc123" \ -H "x-timestamp: 1705315800000" \ -H "x-signature: your_signature_here" ``` ### Example Response ```json theme={null} { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "amount": 149.99, "currency": "USDT", "status": "PAID", "createdAt": "2025-01-15T10:30:00.000Z", "paidAt": "2025-01-15T10:35:00.000Z" } ], "meta": { "page": 1, "pageSize": 20, "totalItems": 150, "totalPages": 8 } } ``` *** ## Payment Status | Status | Description | | ----------- | -------------------------------------------------------- | | `INITIATED` | Payment created, awaiting customer payment | | `PAID` | Customer completed payment successfully | | `EXPIRED` | Payment expired before customer paid | | `FAILED` | Payment failed (network error, insufficient funds, etc.) | *** ## Webhooks Set `webhookUrl` in your payment request to receive real-time status updates. See [Webhooks Guide](/api/v1/webhooks) for payload format, signature verification, and retry policy. *** ## Error Responses | Status | Error | Description | | ------ | ----------------- | ---------------------------------------- | | 400 | Bad Request | Invalid request body or validation error | | 401 | Unauthorized | Invalid API key or signature | | 403 | Forbidden | Payment belongs to another merchant | | 404 | Not Found | Payment not found | | 429 | Too Many Requests | Rate limit exceeded | See [Error Codes](/api/v1/errors) for detailed error handling. *** ## Related Documentation * [Quick Start](/api/v1/quickstart) - Getting started guide * [Authentication](/api/v1/authentication) - HMAC signature details * [Webhooks](/api/v1/webhooks) - Receive payment notifications * [Rate Limits](/api/v1/rate-limits) - API rate limiting * [Errors](/api/v1/errors) - Error codes reference # Quick Start Guide Source: https://docs.ceypay.io/api/v1/quickstart Get started with the CeyPay API in minutes. This guide walks you through creating your first payment. This guide walks you through creating your first payment in under 5 minutes. ## Prerequisites A CeyPay merchant account ([sign up here](https://merchant.ceypay.io)) Completed merchant onboarding and KYC verification Your preferred programming language (Node.js, Python, PHP, etc.) *** ## Base URL Live environment for real transactions ``` https://api.ceypay.io ``` Test environment for integration testing ``` https://sandbox-api.ceypay.io ``` **Getting Started** If you want to integrate CeyPay without signing up for a sandbox account, use the sandbox API key below. **Sandbox API Key:** ``` ak_live_d86855bbd8651fcb5f469203621c88e3.sk_live_6985fca378cd894dee359813bd6bb12f4a2922bc81f023f0d062631e3190ff3e ``` Remember: The `x-api-key` header should contain ONLY the key ID (the part before the dot), NOT the full API key. The secret key is used locally to derive the signing key and is never transmitted. ## Get Started 1. Log in to your [CeyPay Dashboard](https://merchant.ceypay.io) 2. Navigate to **Settings** → **API Keys** 3. Click **Generate New API Key** 4. Give it a descriptive name (e.g., "Production API Key") 5. **Copy the API key immediately** (you'll only see it once!) Your API key looks like: `ak_live_abc123xyz.sk_live_def456uvw` * `ak_live_abc123xyz` - Public key ID (sent in `x-api-key` header) * `sk_live_def456uvw` - Secret key (used locally to derive signing key, NEVER transmitted) **Important**: Store your API key securely (environment variable, secrets manager, etc.) ### Create a Payment Let's create a simple payment for \$100 USDT: ```javascript Node.js theme={null} const crypto = require("crypto"); const axios = require("axios"); // Your API key (store in environment variable) const API_KEY = process.env.CEYPAY_API_KEY; // e.g., ak_live_xxx.sk_live_yyy const [keyId, secret] = API_KEY.split("."); // Derive signing key from secret (do this once) const signingKey = crypto.createHash("sha256").update(secret).digest("hex"); // Create payment async function createPayment() { const timestamp = Date.now().toString(); const method = "POST"; const path = "/v1/payment"; const body = JSON.stringify({ amount: 100, currency: "USDT", goods: [ { name: "Premium Plan", description: "Monthly subscription to premium features", }, ], customerBilling: { email: "customer@example.com", firstName: "John", lastName: "Doe", }, }); // Generate signature using derived key (NOT raw secret) const message = timestamp + method + path + body; const signature = crypto .createHmac("sha256", signingKey) .update(message) .digest("hex"); // Make request - send ONLY key ID, not full API key const response = await axios.post( `https://api.ceypay.io${path}`, JSON.parse(body), { headers: { "Content-Type": "application/json", "x-api-key": keyId, // Only the key ID! "x-timestamp": timestamp, "x-signature": signature, }, }, ); console.log("Payment created:", response.data); console.log("Checkout link:", response.data.checkoutLink); console.log("QR code:", response.data.qrContent); return response.data; } createPayment().catch(console.error); ``` ```python Python theme={null} import os import hmac import hashlib import time import json import requests # Your API key (store in environment variable) API_KEY = os.getenv('CEYPAY_API_KEY') # e.g., ak_live_xxx.sk_live_yyy key_id, secret = API_KEY.split('.') # Derive signing key from secret (do this once) signing_key = hashlib.sha256(secret.encode()).hexdigest() def create_payment(): timestamp = str(int(time.time() * 1000)) method = 'POST' path = '/v1/payment' body_dict = { 'amount': 100, 'currency': 'USDT', 'goods': [ { 'name': 'Premium Plan', 'description': 'Monthly subscription to premium features' } ], 'customerBilling': { 'email': 'customer@example.com', 'firstName': 'John', 'lastName': 'Doe' } } body = json.dumps(body_dict) # Generate signature using derived key (NOT raw secret) message = f"{timestamp}{method}{path}{body}" signature = hmac.new( signing_key.encode(), message.encode(), hashlib.sha256 ).hexdigest() # Make request - send ONLY key ID, not full API key response = requests.post( f'https://api.ceypay.io{path}', json=body_dict, headers={ 'x-api-key': key_id, # Only the key ID! 'x-timestamp': timestamp, 'x-signature': signature } ) data = response.json() print('Payment created:', data) print('Checkout link:', data['checkoutLink']) print('QR code:', data['qrContent']) return data if __name__ == '__main__': create_payment() ``` ```bash cURL theme={null} #!/bin/bash # Your API key API_KEY="ak_live_abc123xyz.sk_live_def456uvw" # Split into key ID and secret KEY_ID="${API_KEY%%.*}" SECRET="${API_KEY#*.}" # Derive signing key from secret SIGNING_KEY=$(echo -n "$SECRET" | sha256sum | cut -d' ' -f1) TIMESTAMP=$(date +%s)000 METHOD="POST" PATH="/v1/payment" BODY='{ "amount": 100, "currency": "USDT", "goods": [ { "name": "Premium Plan", "description": "Monthly subscription to premium features" } ], "customerBilling": { "email": "customer@example.com", "firstName": "John", "lastName": "Doe" } }' # Generate signature using derived signing key MESSAGE="${TIMESTAMP}${METHOD}${PATH}${BODY}" SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$SIGNING_KEY" | cut -d' ' -f2) # Send ONLY key ID in header, not full API key curl -X POST "https://api.ceypay.io${PATH}" \ -H "Content-Type: application/json" \ -H "x-api-key: $KEY_ID" \ -H "x-timestamp: $TIMESTAMP" \ -H "x-signature: $SIGNATURE" \ -d "$BODY" ``` ### Response ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "payId": "bybit_pay_abc123", "merchantTradeNo": "TXN-2025-12345", "amount": 100, "currency": "USDT", "status": "INITIATED", "qrContent": "bybit://pay?id=abc123", "checkoutLink": "https://checkout.bybit.com/pay/abc123", "createdAt": "2025-01-15T10:30:00.000Z", "feeBreakdown": { "exchangeFeePercentage": 1.0, "exchangeFeeAmount": 1.0, "ceypayFeePercentage": 0.5, "ceypayFeeAmount": 0.5, "netAmount": 98.5 } } ``` Save the `id` and `qrContent` from the response. You'll need them to direct customers to payment. You have two options to collect payment: Redirect users to the hosted checkout page: ```javascript theme={null} // Redirect user to the checkout link window.location.href = response.data.checkoutLink; ``` Show a QR code for mobile wallet scanning: ```javascript theme={null} // Show QR code for mobile wallet scanning ``` Set up a webhook endpoint to receive payment status updates. CeyPay uses **ED25519** signatures for security. ```javascript theme={null} const express = require("express"); const crypto = require("crypto"); const app = express(); const CEYPAY_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAs+hotF0u5tdoATwAIezK58lzfMZIoKDTlRgoUU+6Qr4= -----END PUBLIC KEY-----`; // Use raw body for signature verification app.use("/webhooks/ceypay", express.raw({ type: "application/json" })); app.post("/webhooks/ceypay", (req, res) => { const signature = req.headers["x-webhook-signature"]; const timestamp = req.headers["x-webhook-timestamp"]; const rawBody = req.body.toString("utf8"); // Verify signature using ED25519 const message = timestamp + rawBody; try { const isValid = crypto.verify( null, Buffer.from(message), CEYPAY_PUBLIC_KEY, Buffer.from(signature, "base64"), ); if (!isValid) { return res.status(401).send("Invalid signature"); } } catch (err) { return res.status(401).send("Invalid signature"); } // Process webhook const payload = JSON.parse(rawBody); if (payload.status === "PAID") { console.log("Payment successful! Fulfill order:", payload.paymentId); } res.status(200).json({ received: true }); }); app.listen(3000, () => console.log("Webhook server running on port 3000")); ``` See [webhooks.md](/api/v1/webhooks) for complete webhook integration guide. ## Step 5: Test Your Integration ### Test Webhook Endpoint ```bash theme={null} # Derive signing key first SIGNING_KEY=$(echo -n "$SECRET" | sha256sum | cut -d' ' -f1) # Generate signature TIMESTAMP=$(date +%s)000 MESSAGE="${TIMESTAMP}POST/v1/webhooks/test{\"webhookUrl\": \"https://your-app.com/webhooks/ceypay\"}" SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$SIGNING_KEY" | cut -d' ' -f2) curl -X POST https://api.ceypay.io/v1/webhooks/test \ -H "x-api-key: $KEY_ID" \ -H "x-timestamp: $TIMESTAMP" \ -H "x-signature: $SIGNATURE" \ -d '{"webhookUrl": "https://your-app.com/webhooks/ceypay"}' ``` ### Test Payment Flow ## Common Use Cases ### E-commerce Checkout ```javascript theme={null} // Create payment from shopping cart const payment = await createPayment({ amount: cart.total, currency: "USDT", goods: cart.items.map((item) => ({ name: item.name, description: item.description, })), customerBilling: { email: customer.email, firstName: customer.firstName, lastName: customer.lastName, }, webhookUrl: "https://mystore.com/api/webhooks/payment", }); // Redirect to checkout res.redirect(payment.checkoutLink); ``` ### Payment Link for Products ```javascript theme={null} // Create reusable payment link const paymentLink = await createPaymentLink({ name: "Premium Plan - Monthly", description: "Subscription to premium features", amount: 49.99, currency: "USDT", reusable: true, }); // Share the link console.log("Payment link:", `https://pay.ceypay.io/${paymentLink.slug}`); ``` ### Invoice Payment ```javascript theme={null} // Create single-use payment link for invoice const invoice = await createPaymentLink({ name: `Invoice #${invoiceNumber}`, description: "Web development services", amount: invoiceAmount, currency: "USDT", reusable: false, expirationDate: "2025-02-28T23:59:59Z", }); // Email invoice link to customer sendEmail({ to: customer.email, subject: `Invoice #${invoiceNumber}`, body: `Pay your invoice: ${invoice.checkoutUrl}`, }); ``` ## Next Steps ### Learn More Deep dive into HMAC signature Complete webhook integration guide Guide to using payment links Error codes and troubleshooting API rate limiting policies ### Explore API Endpoints **Payments**: * `POST` `/v1/payment` - Create payment * `GET` `/v1/payment/:id` - Get payment details * `GET` `/v1/payment` - List payments **Payment Links**: * `POST` `/v1/payment-link` - Create payment link * `GET` `/v1/payment-link/:id` - Get payment link * `GET` `/v1/payment-link` - List payment links * `PATCH` `/v1/payment-link/:id` - Update payment link * `DELETE` `/v1/payment-link/:id` - Deactivate payment link **Webhooks**: * `GET` `/v1/webhooks/config` - Get webhook configuration * `POST` `/v1/webhooks/test` - Test webhook delivery ### API Reference Full API documentation available at: [https://api.ceypay.io/api-docs](https://api.ceypay.io/api-docs) ### Support * **Email**: [support@ceypay.io](mailto:support@ceypay.io) * **Documentation**: [https://docs.ceypay.io](https://docs.ceypay.io) * **Status**: [https://status.ceypay.io](https://status.ceypay.io) ## Checklist Before going live, ensure you: * [ ] Completed merchant onboarding and KYC * [ ] Generated production API key * [ ] Stored API key securely (environment variable) * [ ] Implemented HMAC signature authentication with derived signing key * [ ] Set up webhook endpoint with signature verification * [ ] Tested payment flow end-to-end * [ ] Implemented error handling * [ ] Configured monitoring and logging * [ ] Reviewed security best practices * [ ] Read rate limiting policies ## Tips ### Security * Never commit API keys to version control * Use environment variables for secrets * Always verify webhook signatures * Use HTTPS for all webhook endpoints * Send only the key ID in `x-api-key`, never the full API key ### Performance * Cache exchange rates when possible * Implement idempotent webhook handling * Use exponential backoff for retries * Monitor rate limit headers ### Testing * Use ngrok for local webhook testing * Create test payments regularly * Monitor webhook delivery logs * Set up alerts for failed webhooks Happy coding! # Rate Limiting Source: https://docs.ceypay.io/api/v1/rate-limits CeyPay's API implements rate limiting to ensure fair usage and system stability. Learn about rate limits, headers, and best practices. ## Overview Rate limits are applied per merchant, per endpoint, with a sliding window algorithm. Each endpoint has its own limit based on expected usage patterns. ## Rate Limits by Endpoint ### Payment Endpoints | Endpoint | Method | Limit | Window | | ------------------ | ------ | ------------ | -------- | | `/v1/payment` | POST | 100 requests | 1 minute | | `/v1/payment/:id` | GET | 300 requests | 1 minute | | `/v1/payment/list` | GET | 200 requests | 1 minute | ### Payment Link Endpoints | Endpoint | Method | Limit | Window | | ----------------------- | ------ | ------------ | -------- | | `/v1/payment-link` | POST | 50 requests | 1 minute | | `/v1/payment-link/:id` | GET | 100 requests | 1 minute | | `/v1/payment-link/list` | GET | 100 requests | 1 minute | | `/v1/payment-link/:id` | PATCH | 50 requests | 1 minute | | `/v1/payment-link/:id` | DELETE | 50 requests | 1 minute | ## Rate Limit Headers Every API response includes rate limit information in headers: ```http theme={null} X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1640000000 ``` ### Header Definitions * **X-RateLimit-Limit**: Maximum requests allowed in the window * **X-RateLimit-Remaining**: Requests remaining in current window * **X-RateLimit-Reset**: Unix timestamp when the limit resets ## Rate Limit Exceeded Response When you exceed the rate limit, you'll receive a 429 response: ```json theme={null} { "statusCode": 429, "message": "Rate limit exceeded. Maximum 100 requests per 60 seconds. Try again in 45 seconds.", "error": "Too Many Requests", "retryAfter": 45 } ``` **Additional Headers:** ```http theme={null} X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1640000000 Retry-After: 45 ``` ## Best Practices ### 1. Monitor Rate Limit Headers Check headers proactively to avoid hitting limits: ```javascript theme={null} function checkRateLimit(response) { const limit = parseInt(response.headers['x-ratelimit-limit']); const remaining = parseInt(response.headers['x-ratelimit-remaining']); const resetTime = parseInt(response.headers['x-ratelimit-reset']); console.log(`Rate limit: ${remaining}/${limit} remaining`); // Warn if approaching limit if (remaining < limit * 0.1) { console.warn(`Approaching rate limit! ${remaining} requests remaining`); } // Calculate time until reset const now = Math.floor(Date.now() / 1000); const secondsUntilReset = resetTime - now; console.log(`Resets in ${secondsUntilReset} seconds`); } const response = await axios.get('/v1/payments'); checkRateLimit(response); ``` ### 2. Implement Exponential Backoff Retry with increasing delays when rate limited: ```javascript theme={null} async function makeRequestWithBackoff(url, options, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await axios(url, options); } catch (error) { if (error.response?.status === 429) { const retryAfter = error.response.data.retryAfter || 60; const backoffTime = Math.min(retryAfter * 1000 * Math.pow(2, i), 60000); console.log(`Rate limited. Waiting ${backoffTime}ms before retry ${i + 1}/${maxRetries}`); await sleep(backoffTime); continue; } throw error; } } throw new Error('Max retries exceeded'); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } ``` ### 3. Cache Responses Cache GET responses to reduce API calls: ```javascript theme={null} const cache = new Map(); const CACHE_TTL = 60000; // 1 minute async function getCachedPayment(paymentId) { const cacheKey = `payment:${paymentId}`; const cached = cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < CACHE_TTL) { console.log('Returning cached payment'); return cached.data; } const payment = await getPayment(paymentId); cache.set(cacheKey, { data: payment, timestamp: Date.now() }); return payment; } ``` ### 4. Batch Requests Use list endpoints instead of multiple single-item requests: ```javascript theme={null} // ❌ Bad: Multiple requests async function getMultiplePayments(paymentIds) { const promises = paymentIds.map(id => getPayment(id)); return Promise.all(promises); // 100 requests if 100 IDs! } // ✅ Good: Single list request async function getMultiplePayments(paymentIds) { const response = await listPayments({ page: 1, pageSize: 100 }); return response.data.filter(p => paymentIds.includes(p.id)); } ``` ### 5. Request Queuing Queue requests to stay within rate limits: ```javascript theme={null} class RateLimiter { constructor(maxRequests, windowMs) { this.maxRequests = maxRequests; this.windowMs = windowMs; this.queue = []; this.requests = []; } async execute(fn) { // Remove old requests outside window const now = Date.now(); this.requests = this.requests.filter(t => now - t < this.windowMs); // Wait if at limit if (this.requests.length >= this.maxRequests) { const oldestRequest = this.requests[0]; const waitTime = this.windowMs - (now - oldestRequest); await sleep(waitTime); } // Execute request this.requests.push(Date.now()); return fn(); } } // Usage const limiter = new RateLimiter(100, 60000); // 100 req/min async function createPaymentSafely(data) { return limiter.execute(() => createPayment(data)); } ``` ### 6. Parallel Request Management Limit concurrent requests: ```javascript theme={null} async function processPaymentsInBatches(paymentData, batchSize = 10) { const results = []; for (let i = 0; i < paymentData.length; i += batchSize) { const batch = paymentData.slice(i, i + batchSize); const batchResults = await Promise.all( batch.map(data => createPayment(data)) ); results.push(...batchResults); // Small delay between batches if (i + batchSize < paymentData.length) { await sleep(1000); } } return results; } ``` ## Rate Limit Strategies by Use Case ### High-Volume Integration For applications with high request volumes: ```javascript theme={null} class ApiClient { constructor(apiKey) { this.apiKey = apiKey; this.limiter = new RateLimiter(100, 60000); this.cache = new Map(); } async getPayment(id) { // Check cache first const cached = this.getCached(`payment:${id}`); if (cached) return cached; // Execute with rate limiting const payment = await this.limiter.execute(() => this.makeRequest(`/v1/payments/${id}`) ); // Cache result this.setCache(`payment:${id}`, payment, 60000); return payment; } getCached(key) { const item = this.cache.get(key); if (item && Date.now() - item.timestamp < item.ttl) { return item.data; } return null; } setCache(key, data, ttl) { this.cache.set(key, { data, timestamp: Date.now(), ttl }); } } ``` ## Monitoring Rate Limits ### Track Usage ```javascript theme={null} class RateLimitMonitor { constructor() { this.metrics = { totalRequests: 0, rateLimitedRequests: 0, requestsByEndpoint: {}, remainingByEndpoint: {} }; } recordRequest(endpoint, response) { this.metrics.totalRequests++; if (!this.metrics.requestsByEndpoint[endpoint]) { this.metrics.requestsByEndpoint[endpoint] = 0; } this.metrics.requestsByEndpoint[endpoint]++; const remaining = parseInt(response.headers['x-ratelimit-remaining']); this.metrics.remainingByEndpoint[endpoint] = remaining; } recordRateLimit(endpoint) { this.metrics.rateLimitedRequests++; } getReport() { return { ...this.metrics, rateLimitPercentage: ( (this.metrics.rateLimitedRequests / this.metrics.totalRequests) * 100 ).toFixed(2) + '%' }; } } const monitor = new RateLimitMonitor(); ``` ### Alert on High Usage ```javascript theme={null} function checkRateLimitHealth(response, endpoint) { const limit = parseInt(response.headers['x-ratelimit-limit']); const remaining = parseInt(response.headers['x-ratelimit-remaining']); const usage = ((limit - remaining) / limit) * 100; if (usage > 80) { console.warn(`⚠️ High rate limit usage for ${endpoint}: ${usage.toFixed(1)}%`); // Send alert to monitoring system sendAlert({ type: 'rate_limit_warning', endpoint, usage, remaining }); } } ``` ## Increasing Rate Limits Default rate limits accommodate most use cases. If you need higher limits: 1. **Email**: [support@ceypay.io](mailto:support@ceypay.io) 2. **Include**: * Your merchant ID * Current usage patterns * Required rate limit * Use case description * Expected request volume We'll review your request and may grant higher limits based on your needs. ## FAQ ### What happens if I exceed the rate limit? You'll receive a 429 response. Wait for the time specified in `retryAfter` (or use the `X-RateLimit-Reset` header) before making new requests. ### Are rate limits per API key or per merchant? Rate limits are per merchant. All API keys for a merchant share the same rate limit pool. ### Do rate limits reset exactly every minute? No. Rate limits use a sliding window algorithm. The window moves continuously, not in fixed 1-minute blocks. ### Can I check my rate limit without making a request? No, but you can make a lightweight GET request and check the headers: ```javascript theme={null} const response = await axios.get('/v1/webhooks/config'); console.log('Rate limit:', response.headers['x-ratelimit-remaining']); ``` ### Do failed requests count toward rate limits? Yes. All requests (successful or failed) count toward your rate limit. ### Are webhook deliveries rate limited? No. Outgoing webhooks from CeyPay to your endpoint are not rate limited (but they have a retry policy). ### What about burst traffic? The sliding window algorithm allows for short bursts. For example, with a 100 req/min limit, you could make 100 requests immediately, but then you'd need to wait \~1 minute before making more. ## Related Documentation * [Error Codes](/api/v1/errors) - Handling 429 errors * [Authentication](/api/v1/authentication) - API authentication * [Quick Start](/api/v1/quickstart) - Getting started * [Webhooks](/api/v1/webhooks) - Webhook integration ## Support Need help with rate limiting? * Email: [support@ceypay.io](mailto:support@ceypay.io) * Documentation: [https://docs.ceypay.io](https://docs.ceypay.io) # Webhook Integration Source: https://docs.ceypay.io/api/v1/webhooks Receive real-time webhook notifications when payment events occur. Learn webhook setup, ED25519 signature verification, and best practices. ## Overview Webhooks enable you to receive automatic notifications when: * A payment is initiated * A customer completes payment * A payment is under user review * A payment expires * A payment fails ## Webhook Events ### payment.initiated Triggered when a payment is created. ```json theme={null} { "paymentId": "550e8400-e29b-41d4-a716-446655440000", "payId": "bybit_pay_abc123", "merchantTradeNo": "ORDER-2025-001", "status": "INITIATED", "amount": 149.99, "currency": "USDT", "timestamp": "2025-01-15T10:30:00.000Z" } ``` ### payment.user\_review Triggered when a customer loads the payment QR code in their wallet app (currently supported for Bybit Pay only). This indicates the customer is actively viewing the payment. ```json theme={null} { "paymentId": "550e8400-e29b-41d4-a716-446655440000", "payId": "bybit_pay_abc123", "merchantTradeNo": "ORDER-2025-001", "status": "USER_REVIEW", "amount": 149.99, "currency": "USDT", "timestamp": "2025-01-15T10:32:00.000Z" } ``` ### payment.paid Triggered when a customer successfully completes payment. ```json theme={null} { "paymentId": "550e8400-e29b-41d4-a716-446655440000", "payId": "bybit_pay_abc123", "merchantTradeNo": "ORDER-2025-001", "status": "PAID", "amount": 149.99, "currency": "USDT", "paidAt": "2025-01-15T10:35:22.000Z", "timestamp": "2025-01-15T10:35:22.000Z" } ``` ### payment.expired Triggered when a payment link expires without payment. ```json theme={null} { "paymentId": "550e8400-e29b-41d4-a716-446655440000", "payId": "bybit_pay_abc123", "merchantTradeNo": "ORDER-2025-001", "status": "EXPIRED", "amount": 149.99, "currency": "USDT", "expiredAt": "2025-01-15T11:30:00.000Z", "timestamp": "2025-01-15T11:30:00.000Z" } ``` ### payment.failed Triggered when a payment fails. ```json theme={null} { "paymentId": "550e8400-e29b-41d4-a716-446655440000", "payId": "bybit_pay_abc123", "merchantTradeNo": "ORDER-2025-001", "status": "FAILED", "amount": 149.99, "currency": "USDT", "failedAt": "2025-01-15T10:32:00.000Z", "timestamp": "2025-01-15T10:32:00.000Z" } ``` ## Webhook Headers Every webhook request includes these headers: ``` X-Webhook-Signature: abc123def456... X-Webhook-Timestamp: 1705315800000 X-Webhook-Attempt: 1 Content-Type: application/json ``` * **X-Webhook-Signature**: Base64-encoded ED25519 signature for verification * **X-Webhook-Timestamp**: Unix timestamp in milliseconds * **X-Webhook-Attempt**: Current delivery attempt (1-5) ## Signature Verification **IMPORTANT**: Always verify webhook signatures to prevent unauthorized requests. CeyPay uses **ED25519** (an EdDSA signature scheme) for signing webhooks. ### CeyPay Public Keys Use these public keys to verify the signatures sent by CeyPay: **Production** ``` -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAs+hotF0u5tdoATwAIezK58lzfMZIoKDTlRgoUU+6Qr4= -----END PUBLIC KEY----- ``` **Sandbox** ``` -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAXD9k6rwT30WwEN9nwvEuPRsfZCJ9nTLIOeQZQxludAk= -----END PUBLIC KEY----- ``` ### Signature Calculation The signature is generated by signing the concatenation of the timestamp and the raw JSON payload: ``` message = X-Webhook-Timestamp + raw_request_body signature = ED25519_SIGN(message, private_key) ``` The `X-Webhook-Signature` header contains the **Base64-encoded** signature. ### Implementation Examples #### Node.js / Express ```javascript theme={null} const crypto = require('crypto'); const express = require('express'); const app = express(); const CEYPAY_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAs+hotF0u5tdoATwAIezK58lzfMZIoKDTlRgoUU+6Qr4= -----END PUBLIC KEY----- `; // Important: Use raw body for signature verification app.use('/webhooks/ceypay', express.raw({ type: 'application/json' })); app.post('/webhooks/ceypay', (req, res) => { const signature = req.headers['x-webhook-signature']; const timestamp = req.headers['x-webhook-timestamp']; const rawBody = req.body.toString('utf8'); // Verify signature using ED25519 const message = timestamp + rawBody; try { const isValid = crypto.verify( null, Buffer.from(message), CEYPAY_PUBLIC_KEY, Buffer.from(signature, 'base64') ); if (!isValid) { console.error('Invalid webhook signature'); return res.status(401).json({ error: 'Invalid signature' }); } } catch (err) { console.error('Signature verification error:', err); return res.status(401).json({ error: 'Invalid signature' }); } // Verify timestamp (prevent replay attacks) const now = Date.now(); const requestTime = parseInt(timestamp); const fiveMinutes = 5 * 60 * 1000; if (Math.abs(now - requestTime) > fiveMinutes) { console.error('Webhook timestamp outside valid window'); return res.status(401).json({ error: 'Invalid timestamp' }); } // Parse and process webhook const payload = JSON.parse(rawBody); console.log('Webhook received:', payload); switch(payload.status) { case 'PAID': // Update order status, fulfill order console.log(`Payment ${payload.paymentId} completed!`); break; case 'EXPIRED': // Cancel order, release inventory console.log(`Payment ${payload.paymentId} expired`); break; case 'FAILED': // Notify customer, offer retry console.log(`Payment ${payload.paymentId} failed`); break; } // Always respond with 200 to acknowledge receipt res.status(200).json({ received: true }); }); app.listen(3000); ``` #### Python / Flask (using cryptography) ```python theme={null} import base64 import time import json from flask import Flask, request, jsonify from cryptography.hazmat.primitives.asymmetric import ed25519 from cryptography.hazmat.primitives import serialization app = Flask(__name__) CEYPAY_PUBLIC_KEY_PEM = b"""-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAs+hotF0u5tdoATwAIezK58lzfMZIoKDTlRgoUU+6Qr4= -----END PUBLIC KEY-----""" public_key = serialization.load_pem_public_key(CEYPAY_PUBLIC_KEY_PEM) @app.route('/webhooks/ceypay', methods=['POST']) def handle_webhook(): signature = request.headers.get('X-Webhook-Signature') timestamp = request.headers.get('X-Webhook-Timestamp') raw_body = request.get_data(as_text=True) # Verify signature message = (timestamp + raw_body).encode() signature_bytes = base64.b64decode(signature) try: public_key.verify(signature_bytes, message) except Exception: return jsonify({'error': 'Invalid signature'}), 401 # Verify timestamp now = int(time.time() * 1000) request_time = int(timestamp) five_minutes = 5 * 60 * 1000 if abs(now - request_time) > five_minutes: return jsonify({'error': 'Invalid timestamp'}), 401 # Parse and process webhook payload = json.loads(raw_body) print(f"Webhook received: {payload}") if payload['status'] == 'PAID': # Update order status print(f"Payment {payload['paymentId']} completed!") elif payload['status'] == 'EXPIRED': # Cancel order print(f"Payment {payload['paymentId']} expired") elif payload['status'] == 'FAILED': # Notify customer print(f"Payment {payload['paymentId']} failed") return jsonify({'received': True}), 200 if __name__ == '__main__': app.run(port=3000) ``` #### PHP ```php theme={null} 'Invalid signature']); exit; } // Verify timestamp $now = time() * 1000; $requestTime = intval($timestamp); $fiveMinutes = 5 * 60 * 1000; if (abs($now - $requestTime) > $fiveMinutes) { http_response_code(401); echo json_encode(['error' => 'Invalid timestamp']); exit; } // Parse and process webhook $payload = json_decode($rawBody, true); switch ($payload['status']) { case 'PAID': // Update order status error_log("Payment {$payload['paymentId']} completed!"); break; case 'EXPIRED': // Cancel order error_log("Payment {$payload['paymentId']} expired"); break; case 'FAILED': // Notify customer error_log("Payment {$payload['paymentId']} failed"); break; } // Always respond with 200 http_response_code(200); echo json_encode(['received' => true]); ?> ``` ## Retry Policy CeyPay implements exponential backoff for failed webhook deliveries: | Attempt | Delay After Previous | | ------- | -------------------- | | 1 | Immediate | | 2 | 1 minute | | 3 | 2 minutes | | 4 | 4 minutes | | 5 | 8 minutes | **Total retry window**: \~15 minutes ### When Are Webhooks Retried? Webhooks are retried when: * Connection timeout (10 seconds) * HTTP status code is not 2xx (200-299) * Network error occurs ### When Do Retries Stop? Retries stop when: * Webhook responds with 2xx status code * Maximum attempts (5) reached * 15 minutes elapsed since first attempt ## Best Practices ### 1. Respond Quickly Return a 200 status code immediately, then process asynchronously: ```javascript theme={null} app.post('/webhooks/ceypay', async (req, res) => { // Verify signature first if (!verifySignature(req)) { return res.status(401).send('Invalid signature'); } // Immediately acknowledge receipt res.status(200).json({ received: true }); // Process webhook asynchronously processWebhookAsync(req.body).catch(err => { console.error('Error processing webhook:', err); }); }); ``` ### 2. Handle Idempotency You may receive the same webhook multiple times. Use `paymentId` to track processed webhooks: ```javascript theme={null} const processedWebhooks = new Set(); function processWebhook(payload) { const paymentId = payload.paymentId; if (processedWebhooks.has(paymentId)) { console.log(`Already processed webhook for payment ${paymentId}`); return; } // Process the webhook updateOrderStatus(paymentId, payload.status); // Mark as processed processedWebhooks.add(paymentId); } ``` ### 3. Use HTTPS Always use HTTPS for webhook endpoints. CeyPay will reject insecure HTTP URLs in production. ### 4. Validate Timestamp Reject webhooks with old timestamps to prevent replay attacks: ```javascript theme={null} const FIVE_MINUTES = 5 * 60 * 1000; const now = Date.now(); const requestTime = parseInt(req.headers['x-webhook-timestamp']); if (Math.abs(now - requestTime) > FIVE_MINUTES) { return res.status(401).send('Timestamp outside valid window'); } ``` ### 5. Log Everything Maintain detailed logs for debugging: ```javascript theme={null} console.log({ timestamp: new Date().toISOString(), paymentId: payload.paymentId, status: payload.status, attempt: req.headers['x-webhook-attempt'], signature: req.headers['x-webhook-signature'] }); ``` ## Testing Webhooks ### Local Development with ngrok 1. Install ngrok: `npm install -g ngrok` 2. Start your local server: `node server.js` 3. Expose via ngrok: `ngrok http 3000` 4. Use the ngrok URL for testing: `https://abc123.ngrok.io/webhooks/ceypay` ### Test Endpoint Use the webhook test endpoint to verify your integration: ```bash theme={null} curl -X POST https://api.ceypay.io/v1/webhooks/test \ -H "Content-Type: application/json" \ -H "x-api-key: your_api_key" \ -H "x-timestamp: $(date +%s)000" \ -H "x-signature: your_signature" \ -d '{"webhookUrl": "https://your-app.com/webhooks/ceypay"}' ``` Response: ```json theme={null} { "success": true, "message": "Test webhook delivered successfully", "deliveryTime": 245 } ``` ## Troubleshooting ### Webhook Not Received **Check:** 1. Webhook URL is publicly accessible (not localhost) 2. Endpoint returns 200 status code 3. No firewall blocking CeyPay's IP addresses 4. Endpoint timeout is > 10 seconds ### Signature Verification Failed **Common causes:** 1. Wrong public key (ensure you use CeyPay's public key) 2. Body parsing issue (use raw body for verification) 3. Timestamp not included in message 4. Character encoding mismatch **Debug:** ```javascript theme={null} console.log('Received signature:', signature); console.log('Timestamp:', timestamp); console.log('Raw body:', rawBody); console.log('Computed message:', timestamp + rawBody); ``` ### Multiple Webhook Deliveries This is normal! Implement idempotency handling using `paymentId`. ### Webhook Timeout Ensure your endpoint responds within 10 seconds: * Return 200 immediately * Process webhook asynchronously * Optimize database queries ## Example Implementations ### E-commerce Order Fulfillment ```javascript theme={null} async function handlePaymentWebhook(payload) { const order = await db.orders.findOne({ paymentId: payload.paymentId }); if (!order) { console.error('Order not found for payment:', payload.paymentId); return; } switch (payload.status) { case 'PAID': await db.orders.update(order.id, { status: 'paid' }); await fulfillOrder(order.id); await sendConfirmationEmail(order.customerEmail); break; case 'EXPIRED': await db.orders.update(order.id, { status: 'expired' }); await releaseInventory(order.items); break; case 'FAILED': await db.orders.update(order.id, { status: 'failed' }); await sendPaymentFailureEmail(order.customerEmail); break; } } ``` ### Subscription Activation ```javascript theme={null} async function handleSubscriptionWebhook(payload) { if (payload.status === 'PAID') { const subscription = await db.subscriptions.findOne({ paymentId: payload.paymentId }); await db.subscriptions.update(subscription.id, { status: 'active', activatedAt: new Date(), expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30 days }); await grantUserAccess(subscription.userId); await sendWelcomeEmail(subscription.userEmail); } } ``` ## Support Need help with webhook integration? * Email: [support@ceypay.io](mailto:support@ceypay.io) * Documentation: [https://docs.ceypay.io](https://docs.ceypay.io) * Status page: [https://status.ceypay.io](https://status.ceypay.io) # Legal Updates Source: https://docs.ceypay.io/changelog/legal-updates Transparency on CeyPay's operational scope, regulatory positioning, and compliance-related updates This Legal & Regulatory Updates Log provides transparency on CeyPay's operational scope, regulatory positioning, and compliance-related updates as the platform evolves. Updates reflect current operating status and pilot-phase constraints. **Maintained by:** Legal & Compliance Team, CeylonCash ## Why Local Settlement Infrastructure Matters More Than Ever The Central Bank of Sri Lanka has clarified that transactions between residents within Sri Lanka must be conducted in Sri Lankan Rupees (LKR), unless specifically authorized. This is an important reminder for merchants and the wider business community. At CeyPay, our model is built around one core principle: * All merchant settlements are conducted in LKR * We do not facilitate resident-to-resident foreign currency transactions * We do not convert LKR into foreign currency to credit local Foreign Currency Accounts Our infrastructure focuses on enabling compliant digital asset inflows while ensuring that settlement within Sri Lanka remains LKR-based and aligned with existing regulatory frameworks. As the ecosystem evolves, structured and compliant payment rails are not optional. They are necessary. CeyPay is committed to building responsible, transparent infrastructure that supports merchants, protects users, and aligns with Sri Lanka's financial regulations. The future of digital payments in Sri Lanka must be innovative, but it must also be compliant. # Product Updates Source: https://docs.ceypay.io/changelog/product-updates Stay up to date with the latest changes and improvements to CeyPay. ## 🎉 Direct Debit is Now Live on CeyPay Direct Debit on CeyPay Set it once. Pay forever. Save your Binance wallet and let payments flow automatically — no more signing every transaction, no more friction. Just smooth, programmable money. **What's new:** * Lock in your Binance wallet for automatic payments * No manual approval required per transaction * Seamless, recurring payment experience out of the box **Binance** is live now. More exchanges coming soon. Learn how to integrate Direct Debit into your application. ## 🚀 Instant Access to CeyPay is Now Live Instant Access to CeyPay No waiting. No paperwork delays. You can start accepting payments today. Merchants can now access the CeyPay dashboard instantly and begin accepting payments even before full approval. During this early access phase, you can receive up to **LKR 5,000** in total transactions. Once you hit the LKR 5,000 limit, payments pause automatically until you complete verification. **Why this matters:** * Test CeyPay before full onboarding * Start generating revenue immediately * Experience real crypto-to-rupee settlement * Zero learning curve Complete verification to unlock unlimited payments, seamless settlements, full dashboard features, and priority merchant support. ## 🎉 Quantity selection for Payment Links is now live Quantity Selection for Payment Links Allow customers to choose the quantity before checkout. Set a maximum limit to control how many units can be purchased per transaction. ## 🎉 Audit log is live Audit Log Feature Merchants can now view all account activity in one place. View detailed logs of payments, withdrawals, and user actions for full transparency. ## 🎉 Pin Products to Your Static QR Page Pin Products to QR Page You can now pin products or payment links to your merchant static QR page. Turn on Show on QR page to display selected items for faster checkout. ## 🎉 New section added Settings -> Preferences Settings Preferences Now you can configure default currency and payment link visibility for your merchant static QR page. ## 🎉 Merchant static QRs are live on CeyPay Merchant Static QR Merchants can now easily receive instant payments with just a QR code. These QRs enable seamless in-store transactions or remote payments when shared digitally, allowing customers to pay from anywhere at any time. ## 🎉 Branches are live on CeyPay CeyPay Branches Feature Manage multiple locations with independent payment tracking and reporting. Merchants can now create branches with optional separate bank accounts for their organizations. Each branch operates as an isolated entity where all resources—payment links, payments, and analytics—are attached to that specific branch. * **Multiple Branches**: Create branches for different locations or business units * **Separate Bank Accounts**: Optionally configure different settlement accounts per branch * **User Management**: Add users and managers under specific branches * **Isolated Resources**: Payment links and payments are tracked per branch * **Branch-Level Reporting**: Track and report on each branch independently * **Centralized Dashboard**: Manage all branches from a single merchant account Perfect for multi-location businesses, franchises, and organizations that need location-based payment tracking and settlement. ## Sandbox Mode We've improved test mode to better simulate the complete payment flow: * More realistic payment confirmation delays * Better error state simulation for debugging * Improved test order tracking in merchant dashboard * Ability to simulate different payment scenarios (success, failure, timeout) ## KuCoin Support Launched CeyPay now supports KuCoin as a payment provider alongside Bybit and Binance Pay. KuCoin KuCoin * Customers can now choose KuCoin as their preferred crypto wallet * Full QR code and deep linking support for mobile payments * Real-time payment verification via webhooks * Available in both WordPress plugin and API ## Binance Pay Support CeyPay now supports Binance Pay as a payment provider alongside Bybit Pay. Binance Pay Binance Pay ## WordPress Plugin Integration CeyPay WordPress Plugin One-click WooCommerce integration for online stores: * Seamless checkout experience * QR code payments for mobile * Deep linking to wallet apps * Full compatibility with WooCommerce 7.0+ ## Bybit Pay Support CeyPay launches with support for Bybit Pay, a leading cryptocurrency payment provider. Bybit Pay Bybit Pay ## CeyPay Merchant Platform Launch CeyPay Payment Gateway We're excited to announce the official launch of CeyPay - accept cryptocurrency payments and receive settlements in Sri Lankan Rupees. CeyPay is a payment processing platform that enables Sri Lankan businesses to accept cryptocurrency payments while receiving settlement in Sri Lankan Rupees (LKR). We bridge the gap between digital currency and traditional banking. ### Multi-Provider Support Accept payments from the most popular crypto wallets. Start with **Bybit Pay** and expand with additional providers as your business grows.
Bybit Bybit Binance Pay Binance Pay KuCoin KuCoin
### Core Platform Features * **Sandbox**: Simulate complete payment flow without real transactions * **Webhook Integration**: Real-time payment notifications with HMAC-SHA256 security * **LKR Settlement**: Receive settlements in Sri Lankan Rupees within 24 hours * **Merchant Dashboard**: Track payments, manage API keys, and view analytics * **API Access**: RESTful API for custom integrations ### Accept 50+ Cryptocurrencies Support for Bitcoin, Ethereum, major altcoins, and stablecoins like USDT and USDC. All crypto automatically converted to LKR and deposited to your bank account. ### Multiple Integration Options * **WordPress Plugin**: One-click WooCommerce integration for online stores * **Payment API**: RESTful API for custom integrations and platforms * **POS & QR Codes**: Physical point-of-sale and QR code payments for in-person sales ### Problems We Solve * **No Crypto Complexity**: Customers pay with crypto, you receive rupees directly * **Licensed & Compliant**: Receive LKR through licensed exchange partners * **Eliminate Chargeback Fraud**: Cryptocurrency transactions are irreversible * **Lower Processing Fees**: Simple, low fee per transaction with no hidden charges * **Global Payments**: Accept borderless payments 24/7 from anywhere in the world
For detailed documentation, see the [Introduction](/introduction) and [API Reference](/api/v1/quickstart). # Frequently Asked Questions Source: https://docs.ceypay.io/guides/faq Get answers to common questions about CeyPay's crypto payment gateway. ## Why Choose CeyPay? 2% processing fee. \$10/month platform fee, charged only if you receive payments. Top 100 merchants get 12 months free platform fee & promotional support. Register by Jan 31st! 1.5% base rate for partners. PSPs and banks can add their own markup on top. Exchange settlement costs fully included in the 2% fee. No hidden charges. Designed to work with Central Bank payment and settlement frameworks for regulatory compliance. Only pay \$10/month platform fee when you receive payments. No transactions? No charge. Completely risk-free pricing for your business. *** ## Quick Answers Setup, onboarding, and device requirements Fees, currencies, and settlement timing Regulations, accounting, and refunds *** ## Getting Started No. Customers pay in crypto, but you receive rupees directly. No crypto handling, no learning curve. CeyPay handles all cryptocurrency complexity behind the scenes. You only interact with LKR. Less than 5 minutes to apply. Once approved, we set up your POS or QR and you can start accepting payments immediately. Complete the online application form KYC verification within 24-48 hours Receive your QR code or POS device No. You can accept payments using a QR code, payment link, or a free POS device if eligible. Most merchants start with a simple QR code that works on any smartphone. POS devices are available for high-volume businesses. Yes. At the moment, CeyPay supports WooCommerce-based online stores. Merchants can get started quickly using our WooCommerce plugin, with other integrations planned for the future. Complete WooCommerce integration guide Install and configure in minutes *** ## Payments & Settlements CeyPay offers zero setup fees and a simple, low fee per transaction. No hidden charges. No setup fees No monthly fees Low per-transaction fee No hidden charges Same-day within 24 hours or next-day depending on your payout schedule. Settlement timing depends on your merchant agreement. Most merchants receive payouts within 24 hours. They can pay using supported crypto assets via our exchange partners. You only receive rupees. Supported cryptocurrencies include Bitcoin, Ethereum, USDT, USDC, and 50+ other popular crypto assets. View the full list in your merchant dashboard. Yes. Customers already use crypto to pay at cafés, hotels, retail stores, and online shops that use CeyPay. This includes WooCommerce merchants and physical merchants, driven largely by strong tourist demand. We don't just provide the rails, we actively bring customers to merchants through community, partnerships, and visibility. See real merchant activity on our live [Leaderboard](https://ceypay.io/leaderboard) *** ## Legal & Compliance Yes. You receive LKR through licensed exchange partners. No claims of legal tender and no regulatory friction. Fully compliant with Sri Lankan regulations Licensed exchange partnerships No legal tender claims No. You receive LKR and record it like a normal sale. Your accountant can follow regular bookkeeping. From an accounting perspective, CeyPay payments are identical to card or cash payments. You receive LKR, invoice in LKR, and report in LKR. Crypto payments are instant and final. Once a payment is completed on-chain, it cannot be reversed or double-spent. This means there are virtually no chargebacks, with over 99.9% of transactions being final. If a merchant chooses to issue a refund, it can be handled manually off-chain or through settlement adjustments, based on mutual agreement with the customer. Unlike card payments, crypto transactions cannot be reversed. Refunds require manual processing through settlement adjustments. Over 99.9% finality rate No chargeback fraud Reduced dispute risk *** ## Support & Community You get local support via the CeylonCash team, backed by a community of 1,000+ active members. Our team is always available to help with onboarding, settlements, and transaction-related questions. [support@ceypay.io](mailto:support@ceypay.io) [Join 1,000+ members](https://t.me/ceypayio) Join our Telegram community for real-time support, updates, and networking with other merchants. *** ## Still have questions? Our team is here to help. Email us at [support@ceypay.io](mailto:support@ceypay.io) or join our Telegram community. # Configuration Source: https://docs.ceypay.io/integrations/wordpress/configuration Complete reference for all CeyPay WordPress Plugin settings. Access CeyPay settings by navigating to **WooCommerce > Settings > Payments > CeyPay** in your WordPress admin. ## General Settings Controls whether CeyPay appears as a payment option at checkout. When disabled, customers will not see CeyPay as a payment method. The text displayed to customers when they select CeyPay at checkout. Use this to explain the payment process or highlight benefits. **Example descriptions:** * "Pay with crypto using Bybit, Binance Pay, or KuCoin" * "Secure cryptocurrency payment - no fees for customers" ## API Credentials These credentials connect your WooCommerce store to the CeyPay payment network. Both fields are required for the gateway to function. Your unique merchant identifier provided by CeyPay. This ID links transactions to your merchant account. **Where to find it:** Log in to your [CeyPay Dashboard](https://ceypay.io) and navigate to Settings > API Credentials. A secret key used to verify that incoming webhook notifications are genuinely from CeyPay. This prevents fraudulent payment confirmations. **Security Note:** Keep this value secret. Never share it publicly or commit it to version control. If either Merchant ID or Webhook Secret is missing, an "Action Needed" badge will appear next to CeyPay in the payment methods list, and an admin notice will prompt you to complete the configuration. ## Test Mode Test Mode allows you to verify your integration without processing real transactions. When enabled, CeyPay uses a test API endpoint instead of the production server. This allows you to simulate the entire payment flow. ### Test Mode Behavior When Test Mode is active: | Feature | Behavior | | ---------------- | -------------------------------------------------------- | | **API Endpoint** | Connects to the CeyPay test server | | **Visibility** | Only administrators can see CeyPay at checkout | | **Simulation** | A "Simulate Success" button appears in the payment modal | | **Logging** | Debug information is written to WooCommerce logs | Test Mode uses a predefined webhook secret (`mock_secret_key`) for signature verification, so you don't need to configure separate test credentials. ## Supported Providers CeyPay supports the following cryptocurrency payment providers:
Bybit Bybit
Binance Pay Binance Pay
KuCoin KuCoin
Customers select their preferred provider in the payment modal after clicking "Place Order". Each provider generates a unique QR code that can be scanned with the corresponding wallet app. ## Checkout Experience ### Classic Checkout On the traditional WooCommerce checkout page, CeyPay displays: 1. **Gateway Title** - Shown as a logo for better brand recognition 2. **Description** - Your configured description text 3. **Provider Selection** - Presented in a modal after order placement ### WooCommerce Blocks Checkout CeyPay fully supports the modern WooCommerce Blocks checkout: * Seamless integration with the block-based checkout editor * Same modal-based provider selection flow * Full support for the Checkout Block and Cart Block If using WooCommerce Blocks, ensure your theme and other plugins are compatible with the Blocks checkout for the best experience. ## Order Processing ### Payment Flow ```mermaid theme={null} sequenceDiagram participant Customer participant WooCommerce participant CeyPay Modal participant CeyPay API participant Crypto Wallet Customer->>WooCommerce: Place Order WooCommerce->>CeyPay Modal: Open Modal Customer->>CeyPay Modal: Select Provider CeyPay Modal->>CeyPay API: Create Payment CeyPay API-->>CeyPay Modal: Return QR Code Customer->>Crypto Wallet: Scan & Pay Crypto Wallet->>CeyPay API: Confirm Payment CeyPay API->>WooCommerce: Webhook Notification WooCommerce->>WooCommerce: Complete Order ``` ### Order Statuses | Status | When Applied | | ------------------- | ------------------------------------------------------ | | **Pending Payment** | Order created, awaiting provider selection | | **Processing** | Payment confirmed via webhook or polling | | **Completed** | Order fulfilled (depends on your WooCommerce settings) | | **Failed** | Payment expired or was declined | ## Webhook URL CeyPay sends payment notifications to your store's webhook endpoint. This URL is automatically configured and follows the pattern: ``` https://yoursite.com/?wc-api=WC_Gateway_CeyPay ``` You don't need to manually configure this URL. WooCommerce handles it automatically. Just ensure your site is accessible from the internet. ## Next Steps Technical details about webhook payloads and signature verification. Learn how to test payments before going live. # Introduction Source: https://docs.ceypay.io/integrations/wordpress/introduction Accept crypto payments on your WooCommerce store with CeyPay WordPress Plugin. CeyPay WordPress Plugin ## What is CeyPay WordPress Plugin? CeyPay WordPress Plugin is a WooCommerce payment gateway that enables your online store to accept cryptocurrency payments through popular platforms like **Bybit**, **Binance Pay**, and **KuCoin**. Designed with both merchants and customers in mind, CeyPay provides a seamless checkout experience with real-time payment verification and automatic order processing. ## Supported Payment Providers
Bybit Bybit
Binance Pay Binance Pay
KuCoin KuCoin
## Key Features Accept payments from Bybit, Binance Pay, and KuCoin. Customers choose their preferred crypto wallet at checkout. Payments are verified in real-time through webhooks and polling, ensuring orders are processed immediately. Deep linking support automatically opens the customer's crypto wallet app on mobile devices. Full compatibility with WooCommerce Blocks checkout for modern storefronts. Built-in test mode with payment simulation for safe integration testing. HMAC-SHA256 signed webhooks ensure payment notifications are authentic. ## How It Works At checkout, the customer selects CeyPay as their payment method and clicks "Place Order". A modal appears where the customer selects their preferred crypto wallet (Bybit, Binance, or KuCoin). A QR code is generated for the selected provider. On mobile, a deep link button opens the wallet app directly. CeyPay monitors the payment status in real-time. Once confirmed, the order is automatically marked as complete. ## Requirements Before installing CeyPay, ensure your environment meets these requirements: | Requirement | Version | | --------------- | --------------------- | | WordPress | 5.8 or higher | | WooCommerce | 7.0 or higher | | PHP | 7.4 or higher | | SSL Certificate | Required for webhooks | Your website must be accessible from the internet with a valid SSL certificate for webhooks to function correctly. ## Quick Links Download the latest version of the CeyPay plugin. Get CeyPay installed and configured in minutes. Detailed settings reference for all CeyPay options. Technical documentation for webhook integration. External services used and how data is handled. Common issues and their solutions. # Privacy & External Services Source: https://docs.ceypay.io/integrations/wordpress/privacy External services used by the CeyPay WordPress plugin and how data is handled. The CeyPay WordPress plugin connects to external services to process payments, load fonts, and optionally collect analytics. This page documents all external services, what data is sent, and when. This information is provided for transparency and compliance with WordPress.org guidelines. ## Required Services These services are essential for the plugin to function and cannot be disabled. **Purpose**: Process cryptocurrency payments and generate QR codes for Bybit, Binance Pay, and KuCoin.
Service Provider CeyPay
Service URLs Production: `https://api.ceypay.io/`
Test Mode: `https://sandbox-api.ceypay.io/`
Data Sent • Order total and currency
• Order ID and merchant ID
• No customer personal data or payment credentials
When Every time a customer initiates a CeyPay payment at checkout
**Purpose**: Send error logs and diagnostic information to help identify and fix payment integration issues.
Service Provider CeyPay
Service URL `https://dev.ceylon.cash/ceypay/debugger/logs/report`
Data Sent • Error messages and stack traces
• Plugin version, WordPress version, WooCommerce version
• No customer personal data or payment details
When When an error occurs during payment processing
**Purpose**: Load the Lato font to ensure consistent typography in the payment modal.
Service Provider Google LLC
Service URL `https://fonts.googleapis.com/`
Data Sent • User's IP address (standard web request)
• No personal or payment information
When Every time the checkout page with CeyPay is loaded
## Optional Services These services are **disabled by default** and require explicit opt-in through the plugin settings. You must enable "Usage Analytics" in WooCommerce → Settings → Payments → CeyPay to activate them. **Purpose**: Collect anonymous payment flow events to help improve the plugin.
Service Provider Google LLC
Service URLs `https://www.googletagmanager.com/`
`https://www.google-analytics.com/`
Data Sent • Anonymous events (e.g., provider selection, payment completion rates)
• Merchant ID (not customer data)
• No personal customer data or transaction amounts
When On checkout and cart pages when analytics is enabled
Go to **WooCommerce → Settings → Payments → CeyPay** Uncheck **"Enable usage analytics"** Click **Save changes** at the bottom of the page
**Purpose**: Track checkout and purchase conversion events to measure payment gateway effectiveness.
Service Provider Meta Platforms, Inc.
Service URLs `https://connect.facebook.net/`
`https://www.facebook.com/tr`
Data Sent • PageView event on all pages
• InitiateCheckout event on checkout page
• Purchase event with order total and currency on order confirmation page
When On checkout and order confirmation pages when analytics is enabled
Go to **WooCommerce → Settings → Payments → CeyPay** Uncheck **"Enable usage analytics"** Click **Save changes** at the bottom of the page
## Data Privacy Summary The plugin only sends order totals and currency information to CeyPay for payment processing. No personal customer information (names, emails, addresses) or payment credentials are transmitted to CeyPay servers. All customer data is processed by WooCommerce and remains on your server. No. Customer personal information is not shared with any third-party services. Only order totals and merchant IDs are sent to CeyPay's payment API to generate QR codes. Optional analytics services (Google Analytics and Facebook Pixel) only receive anonymous event data if explicitly enabled by the merchant. All communication with CeyPay APIs uses HTTPS encryption (SSL/TLS). Webhook notifications are signed with HMAC-SHA256 signatures to prevent tampering and verify authenticity. See the [Webhooks](/integrations/wordpress/webhooks) documentation for technical details. Yes. Analytics features (Google Analytics and Facebook Pixel) are **disabled by default**. They only activate when you explicitly check "Enable usage analytics" in the plugin settings. To disable analytics at any time, go to WooCommerce → Settings → Payments → CeyPay and uncheck the option. The CeyPay plugin is designed with GDPR principles in mind: * Minimal data collection (only what's needed for payment processing) * No customer personal data sent to external services * Optional analytics require explicit opt-in * Transparent documentation of all external services You should still conduct your own GDPR compliance review based on your specific use case and jurisdiction. *** ## Need Help? Have questions about data handling? Reach out to our support team. Read our full privacy policy for detailed information. # Quickstart Source: https://docs.ceypay.io/integrations/wordpress/quickstart Get CeyPay WordPress Plugin installed and accepting crypto payments in minutes. This guide will walk you through installing the CeyPay WordPress plugin, configuring your credentials, and verifying your setup. You'll be ready to accept crypto payments in under 10 minutes. ## What You'll Need With admin access Installed and active Merchant credentials ## Installation Download the latest version of the CeyPay WooCommerce plugin or install directly from WordPress. Choose one of the following methods to install the CeyPay Payment Gateway plugin. The easiest way to install CeyPay is directly from your WordPress admin panel. Log in to your WordPress admin dashboard and go to **Plugins > Add New**. In the search box, type "CeyPay Payment Gateway" and press Enter. Click **Install Now** on the CeyPay Payment Gateway plugin card. Once installed, click **Activate** to enable the plugin. If you have the plugin ZIP file, you can upload it manually. Download the `ceypay-payment-gateway.zip` file from the official source. Go to **Plugins > Add New** and click **Upload Plugin** at the top. Choose the ZIP file and click **Install Now**. Click **Activate** after the installation completes. For advanced users who prefer manual file management. Extract the `ceypay-payment-gateway.zip` file on your local machine. Use an FTP/SFTP client to connect to your web server. Upload the `ceypay-payment-gateway` folder to `/wp-content/plugins/`. Go to **Plugins** in WordPress admin and activate CeyPay Payment Gateway. ## Initial Configuration After activation, you'll see an admin notice prompting you to complete the setup. This only takes a few minutes! Navigate to **WooCommerce → Settings → Payments** in your WordPress admin dashboard. Find **CeyPay** in the list of available payment methods. Toggle the switch to **enable** it, then click **Manage** or click the gateway name. Fill in the required fields: Your unique merchant identifier from the CeyPay dashboard. Example: `MERCH-123456` The secret key used to verify webhook signatures. Found in your CeyPay dashboard under **Settings → API Settings**. Keep this secure! Click **Save changes** at the bottom of the page to apply your configuration. **Success!** Once configured, CeyPay will appear as a payment option on your checkout page. Customers can now pay with Bybit, Binance Pay, or KuCoin. ## Get Your API Credentials Don't have a CeyPay account yet? You can apply for a merchant account in minutes and get instant access to test mode credentials. Visit [merchant.ceypay.io](https://merchant.ceypay.io) and sign up for a merchant account. You'll receive test credentials immediately. Complete the merchant verification process to unlock production access and real payment processing. In your CeyPay dashboard, navigate to **Settings → API Settings** or **Integration** section. Copy your **Merchant ID** and **Webhook Secret**. Keep these secure—they're used to authenticate API requests. Your webhook secret should be treated like a password. Never share it publicly or commit it to version control. ## Verify Your Setup Always test your integration before going live. Test mode lets you simulate the complete payment flow without processing real transactions. In the CeyPay settings (WooCommerce → Settings → Payments → CeyPay), check **Enable Test Mode**. This allows you to test without real transactions. Add a product to your cart and proceed to checkout. Select **CeyPay** as the payment method. After clicking "Place Order", the payment modal should appear with QR codes for Bybit, Binance Pay, and KuCoin. In test mode, you'll see a **"Simulate Success"** button. Click the simulate button and confirm the order is marked as **"Processing"** or **"Completed"** in WooCommerce → Orders. **Remember to disable Test Mode before accepting real payments!** Test Mode is only visible to administrators and prevents actual payment processing. Check that: * JavaScript is enabled in your browser * Your theme is not blocking modal overlays * There are no JavaScript errors in the browser console (F12 → Console) * The plugin is activated and WooCommerce is working properly Test mode simulates the payment flow without requiring real wallets. To test with actual Bybit, Binance Pay, or KuCoin accounts, disable test mode and use small amounts. Make sure you're in a test environment before processing real payments. A successful test confirms that: * The plugin is installed and activated correctly * Your API credentials are valid * Webhooks are configured properly (if using real mode) * WooCommerce can process orders through CeyPay You're ready to accept real crypto payments! *** ## You're All Set! 🎉 Congratulations! You've successfully installed and configured the CeyPay WordPress plugin. Your store is now ready to accept cryptocurrency payments from Bybit, Binance Pay, and KuCoin. ## Next Steps Explore all available settings and customize the checkout experience. Learn how to thoroughly test your integration before going live. Understand how webhooks work and verify they're configured correctly. Common issues and solutions to keep your integration running smoothly. ## Need Help? Our support team is here to help with any questions. Connect with other merchants on Telegram. # Testing Source: https://docs.ceypay.io/integrations/wordpress/testing How to test your CeyPay WordPress Plugin integration before accepting real payments. Thoroughly testing your payment integration is essential before going live. CeyPay provides a built-in Test Mode that simulates the entire payment flow without processing real cryptocurrency transactions. ## Enabling Test Mode Navigate to **WooCommerce > Settings > Payments > CeyPay** in your WordPress admin. Check the box labeled **Enable Test Mode**. Click **Save changes** at the bottom of the page. When Test Mode is enabled, only logged-in administrators can see and use the CeyPay payment option. Regular customers will not see it at checkout. ## Testing the Payment Flow ### Complete Test Transaction Browse your store as an administrator and add one or more products to your cart. Go to the checkout page and fill in the required billing details. Choose **CeyPay** as your payment method. You should see the CeyPay logo and description. Click **Place Order** to initiate the payment process. In the modal that appears, select a crypto provider:
Bybit Bybit
Binance Pay Binance Pay
KuCoin KuCoin
A QR code will be generated. In Test Mode, you'll also see a **"Simulate Success (Test Mode)"** button. Click the simulation button to complete the test payment. You should be redirected to the Order Received page, and the order status should change to "Processing".
### What to Verify Use this checklist to ensure your integration is working correctly: * [ ] CeyPay appears in the payment methods list - \[ ] Logo displays correctly (both light and dark themes if applicable) - \[ ] Description text is visible * [ ] Modal opens after clicking "Place Order" - \[ ] All providers are displayed (Bybit, Binance, KuCoin) - \[ ] Provider selection works correctly - \[ ] QR code loads after selecting a provider - \[ ] "Simulate Success" button appears in Test Mode * [ ] Order is created with "Pending Payment" status initially - \[ ] Order transitions to "Processing" after simulated payment - \[ ] Order notes show payment confirmation - \[ ] Customer receives order confirmation email * [ ] Modal is responsive on mobile devices - \[ ] Deep link button appears for mobile users - \[ ] QR code is scannable on smaller screens ## Testing Webhooks While Test Mode simulates immediate payment confirmation, you should also verify that webhooks work correctly. ### Check Webhook Logs Go to **WooCommerce > Status > Logs** to access the log files. Look for log files prefixed with `ceypay-`. These contain debug information about webhook processing. Check for successful webhook receipts and any error messages. ### Webhook Testing Tools For more thorough webhook testing, you can use tools like: * **ngrok** - Create a public URL for your local development environment * **Webhook.site** - Inspect incoming webhook payloads * **RequestBin** - Debug webhook requests These tools are for development/testing only. Never expose your production webhook secret or use these tools on a live store. ## Testing WooCommerce Blocks If your store uses the WooCommerce Blocks checkout: Ensure your checkout page uses the Checkout Block rather than the shortcode-based checkout. Follow the same testing steps as above. The CeyPay payment option should integrate seamlessly. Open browser developer tools and check for any JavaScript errors during checkout. ## Testing Edge Cases ### Payment Expiration To test expired payments: 1. Start a test payment but don't click "Simulate Success" 2. Wait for the payment to expire (or close the modal) 3. Verify the order remains in "Pending Payment" status ### Failed Payments Failed payments can be simulated by: 1. Using invalid test data (if supported by the test API) 2. Manually updating order status to "Failed" in WooCommerce admin 3. Checking that appropriate error messages display ### Multiple Payments Test scenarios with: * Multiple items in cart * Various currencies (if your store supports them) * Different customer billing information ## Going Live Checklist Before disabling Test Mode and accepting real payments, verify: * [ ] All test transactions complete successfully - \[ ] Webhooks are received and processed correctly - \[ ] Order emails are sent properly - \[ ] Order statuses update as expected - \[ ] Mobile experience works correctly - \[ ] Error handling works (network failures, etc.) - \[ ] **Merchant ID** is set to your production credentials - \[ ] **Webhook Secret** is set to your production key - \[ ] SSL certificate is valid and not expired - \[ ] Site is accessible from the internet ## Disabling Test Mode Once testing is complete: Go to **WooCommerce > Settings > Payments > CeyPay**. Uncheck **Enable Test Mode**. Confirm your production **Merchant ID** and **Webhook Secret** are entered correctly. Click **Save changes**. Your store is now ready to accept real cryptocurrency payments through CeyPay. ## Next Steps Deep dive into webhook payloads and signature verification. Solutions for common issues you might encounter. # Troubleshooting Source: https://docs.ceypay.io/integrations/wordpress/troubleshooting Solutions for common issues with CeyPay WordPress Plugin. This guide covers the most common issues you might encounter when using CeyPay and how to resolve them. ## Installation Issues **Symptoms:** After installation, CeyPay doesn't appear in WooCommerce > Settings > Payments. **Solutions:** 1. Ensure WooCommerce is installed and activated 2. Check that CeyPay plugin is activated in Plugins > Installed Plugins 3. Clear any caching plugins or server cache 4. Verify PHP version is 7.4 or higher in Tools > Site Health ```php theme={null} // Check PHP version phpinfo(); // Should show PHP 7.4+ ``` **Symptoms:** Error message when trying to activate the plugin. **Common causes:** * PHP version too low (requires 7.4+) * WooCommerce not active * File permissions issue **Solution:** Check the error message for specifics. Ensure all requirements are met before activating. **Symptoms:** Site crashes or behaves unexpectedly after installing CeyPay. **Solution:** 1. Deactivate all plugins except WooCommerce and CeyPay 2. Test if the issue persists 3. Reactivate plugins one by one to identify the conflict 4. Contact support with the conflicting plugin name ## Checkout Issues **Symptoms:** CeyPay doesn't appear as a payment option on the checkout page. **Possible causes:** 1. **Gateway disabled** - Enable it in WooCommerce > Settings > Payments 2. **Test Mode enabled** - Only admins see CeyPay in test mode; log in as admin 3. **Missing credentials** - Configure Merchant ID and Webhook Secret 4. **Currency not supported** - Verify your store currency is supported **Quick check:** * Look for "Action Needed" badge next to CeyPay in payment settings * Review the admin notice at the top of WordPress admin pages **Symptoms:** Clicking "Place Order" doesn't open the payment modal. **Solutions:** 1. Check browser console for JavaScript errors (F12 > Console) 2. Ensure jQuery is loaded (required by WooCommerce) 3. Try a different browser or incognito mode 4. Disable other JavaScript-heavy plugins temporarily 5. Check if a page builder is interfering with checkout **Console command to verify:** ```javascript theme={null} // Check if CeyPay scripts are loaded console.log(typeof ceypay_params); ``` **Symptoms:** Provider selection works, but no QR code appears. **Causes:** * API connection failure * Invalid Merchant ID * Server cannot make outbound HTTPS requests **Solutions:** 1. Verify your Merchant ID is correct 2. Check if your server can reach external APIs 3. Review WooCommerce logs for API errors 4. Contact support if the issue persists **Symptoms:** Error message about invalid parameters when generating QR code. **Solution:** This usually indicates missing order data. Ensure: * All required checkout fields are filled * The cart contains valid products * The currency is supported ## Webhook Issues **Symptoms:** Customer completes payment, but order status doesn't update. **Causes:** 1. Webhooks not reaching your server 2. Signature verification failing 3. Site not publicly accessible **Diagnostic steps:** 1. Check WooCommerce logs for webhook entries 2. Verify your site is accessible from the internet 3. Ensure SSL certificate is valid 4. Confirm webhook secret matches exactly **Temporary workaround:** The plugin includes status polling, so the customer should see success if they keep the modal open. The webhook will update the order for admin visibility. **Symptoms:** Logs show "Webhook signature mismatch" errors. **Solution:** 1. Copy the Webhook Secret directly from your CeyPay dashboard 2. Paste it into CeyPay settings without any extra spaces 3. Save settings and test again The webhook secret is case-sensitive. Copy it exactly as shown. **Symptoms:** No webhook entries in logs, even after successful test payments. **Checklist:** * [ ] Site has valid SSL certificate (HTTPS required) * [ ] Site is not on localhost (use ngrok for local testing) * [ ] Firewall allows incoming POST requests * [ ] .htaccess or server config doesn't block the API endpoint * [ ] The webhook URL is correctly registered in CeyPay dashboard **Test your endpoint:** ```bash theme={null} curl -X POST https://yoursite.com/?wc-api=WC_Gateway_CeyPay # Should return 400 (Bad Request) - this means the endpoint is reachable ``` ## Order Issues **Symptoms:** Multiple orders created for a single payment. **Cause:** Customer clicking "Place Order" multiple times. **Solutions:** 1. The plugin should prevent this, but if it occurs: 2. Check for JavaScript errors that might prevent modal from opening 3. Ensure the checkout page doesn't have duplicate form submissions 4. Review any caching plugin settings **Symptoms:** Order shows as Processing, but no payment in crypto wallet. **Cause:** This should only happen in Test Mode with simulated payments. **Solution:** 1. Verify Test Mode is disabled for production 2. Check that production Merchant ID is configured 3. Review CeyPay dashboard for transaction records 4. Contact CeyPay support with order details **Symptoms:** The payment amount in the modal doesn't match the order total. **Cause:** Currency conversion or rounding issues. **Solution:** 1. Verify your store currency settings 2. Check if any plugins modify order totals 3. The USDT amount may differ due to exchange rates ## WooCommerce Blocks Issues **Symptoms:** CeyPay appears but doesn't function correctly with the Checkout Block. **Solutions:** 1. Ensure WooCommerce Blocks is up to date 2. Check browser console for JavaScript errors 3. Verify the Checkout Block is properly configured 4. Try switching to classic checkout temporarily to isolate the issue **Symptoms:** Payment modal appears but looks broken or unstyled. **Solutions:** 1. Clear all caches (page cache, browser cache, CDN cache) 2. Check if your theme overrides WooCommerce styles 3. Ensure CeyPay CSS file is loading (check Network tab in dev tools) ## Mobile Issues **Symptoms:** Clicking the app button on mobile doesn't open the crypto wallet. **Causes:** * Wallet app not installed * Deep link not supported by browser * Browser blocking the redirect **Solutions:** 1. Ensure the customer has the relevant wallet app installed 2. Try opening the link in the default mobile browser 3. The QR code remains available as a fallback **Symptoms:** Payment modal is difficult to use on mobile devices. **Solution:** The modal should be responsive by default. If issues occur: 1. Check if your theme has mobile-specific CSS that conflicts 2. Disable any mobile optimization plugins temporarily 3. Test in a default WordPress theme (like Storefront) ## Debug Mode For persistent issues, enable debug logging: Go to WooCommerce > Settings > Payments > CeyPay and enable Test Mode. Attempt the action that's causing problems. Navigate to WooCommerce > Status > Logs and find the `ceypay-*` log file. Look for error messages, failed API calls, or signature mismatches. ## Getting Support If you can't resolve an issue using this guide: Contact our support team directly for personalized assistance. Report bugs or request features on our GitHub repository. ### Information to Include When contacting support, please provide: * WordPress version * WooCommerce version * CeyPay plugin version * PHP version * Theme name and version * Relevant log entries * Steps to reproduce the issue * Screenshots if applicable You can find version information at **WooCommerce > Status > System Status**. # Webhooks Source: https://docs.ceypay.io/integrations/wordpress/webhooks Technical reference for CeyPay WordPress Plugin webhook integration and signature verification. Webhooks allow CeyPay to notify your WooCommerce store in real-time when payment events occur. This ensures orders are updated immediately when customers complete their payments. ## Webhook Endpoint CeyPay sends webhook notifications to your store's WooCommerce API endpoint: ``` https://yoursite.com/?wc-api=WC_Gateway_CeyPay ``` The webhook URL is generated automatically by WooCommerce. You can find the exact URL by checking the webhook configuration in your CeyPay dashboard or by examining the `webhookUrl` parameter in API requests. ## Webhook Events CeyPay sends webhooks for the following payment status changes: | Status | Description | WooCommerce Action | | --------- | ------------------------------------- | ---------------------------- | | `SUCCESS` | Payment completed successfully | Order marked as "Processing" | | `PAID` | Payment confirmed (alias for SUCCESS) | Order marked as "Processing" | | `EXPIRED` | Payment window expired | Order marked as "Failed" | | `FAILED` | Payment was declined or errored | Order marked as "Failed" | ## Webhook Payload ### Request Headers Each webhook request includes these headers for verification: | Header | Description | | --------------------- | ---------------------------------------- | | `X-Webhook-Signature` | HMAC-SHA256 signature of the payload | | `X-Webhook-Timestamp` | Unix timestamp when the webhook was sent | | `Content-Type` | Always `application/json` | ### Request Body The webhook payload is a JSON object containing payment details: Unique identifier for the payment transaction. This is stored in the order metadata as `_ceypay_transaction_id`. Alternative field name for the payment ID. The plugin accepts either `paymentId` or `transactionId`. Current payment status. One of: `SUCCESS`, `PAID`, `EXPIRED`, `FAILED`. ### Example Payload ```json theme={null} { "paymentId": "pay_abc123xyz789", "status": "SUCCESS", "amount": 100.0, "currency": "USD", "provider": "BINANCE" } ``` ## Signature Verification All webhook requests are signed using HMAC-SHA256 to ensure authenticity. The plugin automatically verifies signatures before processing any webhook. ### How Signatures Work 1. CeyPay concatenates the timestamp and JSON payload 2. The combined string is hashed using HMAC-SHA256 with your webhook secret 3. The resulting signature is included in the `X-Webhook-Signature` header ### Verification Process The plugin verifies signatures using this logic: ```php theme={null} // Construct signature payload $signature_payload = $timestamp . $json_body; // Compute expected signature $expected_signature = hash_hmac('sha256', $signature_payload, $webhook_secret); // Compare signatures (timing-safe) if (hash_equals($expected_signature, $received_signature)) { // Signature valid - process webhook } else { // Signature invalid - reject request } ``` If signature verification fails, the webhook returns a `401 Unauthorized` response and the request is logged. Check your webhook secret configuration if you see signature mismatch errors. ### Test Mode Signatures When Test Mode is enabled, the plugin uses a predefined secret (`mock_secret_key`) for signature verification. This allows the test server to send valid webhooks without configuring your production credentials. ## Order Matching The plugin matches incoming webhooks to WooCommerce orders using the transaction ID stored in order metadata. ### Lookup Process 1. Extract `paymentId` or `transactionId` from webhook payload 2. Query WooCommerce orders for meta key `_ceypay_transaction_id` 3. If found, update the order status based on payment status 4. If not found, log the error (order may have been deleted) ### Stored Order Metadata The following metadata is stored on each order: | Meta Key | Description | | ------------------------ | -------------------------------------------------- | | `_ceypay_transaction_id` | CeyPay transaction identifier | | `_ceypay_provider` | Selected payment provider (BYBIT, BINANCE, KUCOIN) | | `_ceypay_qr_code_url` | QR code content URL | | `_ceypay_deep_link` | Deep link URL for mobile wallets | **Available Providers:**
Bybit Bybit
Binance Pay Binance Pay
KuCoin KuCoin
## Handling Webhook Responses ### Success Response Return HTTP `200 OK` to acknowledge successful webhook processing: ```http theme={null} HTTP/1.1 200 OK ``` ### Error Responses | Status Code | Meaning | | --------------------------- | ----------------------------- | | `400 Bad Request` | Invalid or malformed payload | | `401 Unauthorized` | Signature verification failed | | `405 Method Not Allowed` | Non-POST request received | | `500 Internal Server Error` | Webhook secret not configured | ## Debugging Webhooks ### Enable Logging In Test Mode, webhook activity is automatically logged to WooCommerce logs. Access logs at: **WooCommerce > Status > Logs** → Select files prefixed with `ceypay-` ### Log Entries Typical log entries include: ``` Webhook received: {"paymentId":"pay_123","status":"SUCCESS"} Signature Check: Received: abc... Computed: abc... Match: YES Processing webhook for Transaction ID: pay_123, Status: SUCCESS Order #456 marked as paid. ``` ### Common Issues **Cause:** Webhook secret doesn't match between CeyPay dashboard and plugin settings. **Solution:** Copy the webhook secret directly from your CeyPay dashboard and paste it into the plugin settings. Avoid adding extra spaces or characters. **Cause:** The order was deleted, or the customer abandoned checkout before the QR code was generated. **Solution:** This is usually expected behavior for abandoned checkouts. No action required. **Cause:** Server firewall blocking incoming requests, or site not accessible from internet. **Solution:** Ensure your site has a valid SSL certificate and is publicly accessible. Check server firewall rules. ## Security Best Practices Never expose your webhook secret in client-side code, version control, or public logs. Always use HTTPS for your webhook endpoint. HTTP webhooks should be rejected. Always verify webhook signatures. The plugin does this automatically. Handle duplicate webhooks gracefully. The plugin checks order status before updating. ## Retry Policy If your webhook endpoint is unavailable, CeyPay may retry the webhook. The plugin handles this by checking if the order is already processed before making changes. The plugin includes fallback payment status polling, so even if a webhook fails, payments will still be confirmed when the customer's browser polls for status updates. ## Next Steps Solutions for common webhook and integration issues. Review all available plugin settings. # Accept Crypto. Get Paid in Rupees. Source: https://docs.ceypay.io/introduction CeyPay makes accepting cryptocurrency simple. Customers pay in crypto, you receive LKR. No wallets, no crypto knowledge, no extra steps. Welcome ## What is CeyPay? CeyPay is a payment processing platform that enables Sri Lankan businesses to accept cryptocurrency payments while receiving settlement in Sri Lankan Rupees (LKR). We bridge the gap between digital currency and traditional banking. Customers pay with crypto, you receive rupees directly. No wallets, no blockchain knowledge required. Receive LKR through licensed exchange partners. Fully compliant with Sri Lankan regulations. ## Problems We Solve **The Problem**: Most merchants want to tap into the crypto market but are intimidated by wallets, private keys, and blockchain complexity. Setting up crypto payments traditionally requires technical expertise. **CeyPay's Solution**: Accept crypto without learning how it works. We handle all the technical complexity. You receive familiar LKR payments in your bank account, just like traditional payment gateways. **The Problem**: Credit card chargebacks cost merchants billions annually. Fraudulent disputes can reverse payments weeks or months after delivery, with merchants losing both product and payment. **CeyPay's Solution**: Cryptocurrency transactions are irreversible. Once payment is confirmed, it's final. Eliminate chargeback fraud completely while accepting legitimate payments globally. **The Problem**: Traditional card rails charge 2-4% plus gateway fees, currency conversion costs, and international transaction fees. These costs eat into profit margins, especially for cross-border sales. **CeyPay's Solution**: Simple, low fee per transaction with no hidden charges. No setup fees, no monthly minimums. Pay only for successful transactions. **The Problem**: Traditional payment processors have geographic limitations, currency restrictions, and banking downtime. International payments face delays and high conversion fees. **CeyPay's Solution**: Accept borderless payments 24/7 from anywhere in the world. No currency restrictions, no banking hours, no weekend delays. Settlement within 24 hours or next business day. ## Core Features ### Accept 50+ Cryptocurrencies Bitcoin, Ethereum, and major altcoins supported through licensed exchange partners USDT, USDC, and other stablecoins for price stability and lower volatility All crypto automatically converted to LKR and deposited to your bank account ### Multiple Integration Options CeyPay Integration Options One-click WooCommerce integration for online stores RESTful API for custom integrations and platforms Physical point-of-sale and QR code payments for in-person sales ### Fast & Secure Settlement * **24-Hour Settlement** - Receive LKR within 24 hours or next business day * **Licensed Partners** - All transactions processed through licensed crypto exchanges * **Real-Time Verification** - Instant payment confirmation via webhooks * **Secure Webhooks** - HMAC-SHA256 signed webhooks prevent fraud ## Supported Payment Providers CeyPay integrates with major cryptocurrency wallets and exchanges:
Bybit Bybit
Binance Pay Binance Pay
KuCoin KuCoin
## Who Uses CeyPay? WooCommerce, custom platforms, and online retailers accepting global crypto payments Hotels, cafés, and travel services serving international tourists with crypto Physical stores using QR codes and POS systems for in-person crypto payments SaaS, subscriptions, and digital products sold to global crypto-native customers ## How It Works Customer selects CeyPay at checkout and chooses their preferred wallet (Bybit, Binance Pay, or KuCoin) Customer scans QR code or uses deep link to complete the cryptocurrency payment from their wallet CeyPay verifies payment in real-time and notifies your system via webhook. Order processes automatically. Crypto is auto-converted to LKR and deposited to your bank account within 24 hours or next business day ## Quick Start Guide [Get onboarded](https://merchant.ceypay.io/) in approximately 5 minutes. Provide business details and banking information for LKR settlements. Select your integration approach based on your platform: * **WooCommerce**: Install WordPress plugin (5 minutes) * **Custom Platform**: Integrate using REST API * **Physical Store**: Request POS/QR code setup Add your merchant credentials for Bybit, Binance Pay, and/or KuCoin. Configure webhook endpoints for real-time payment notifications. Use test mode to simulate complete payment flows. Once verified, enable live mode and start accepting crypto payments. ## Integration Paths Complete guide for WooCommerce stores. Install, configure, and go live in minutes. RESTful API reference for custom integrations and platform developers. Common questions about fees, settlement times, supported currencies, and compliance. Apply for merchant account and start accepting crypto payments today. ## Why CeyPay? No upfront costs, no monthly minimums. Pay only per successful transaction. Receive LKR within 24 hours or next business day, faster than traditional rails. Accept Bitcoin, Ethereum, USDT, USDC, and 50+ digital assets. Eliminate chargeback fraud with irreversible crypto transactions. Accept borderless payments 24/7 with no geographic restrictions. Received funds treated as normal sales. No special accounting required. ## Need Help? * **Support Email**: [support@ceypay.io](mailto:support@ceypay.io) * **Get Onboarded**: [merchant.ceypay.io](https://merchant.ceypay.io/) * **Homepage**: [ceypay.io](https://ceypay.io) # Report Abuse Source: https://docs.ceypay.io/report-abuse Report fraudulent activity or abuse of the CeyPay payment system. ## Report Suspicious Activity CeyPay is committed to maintaining a safe and secure payment environment for all merchants and customers. If you encounter fraudulent activity, abuse, or suspicious transactions, please report it immediately. ## What to Report Report unauthorized or suspicious payment activities on your account. Report fake emails, websites, or messages claiming to be from CeyPay. Report if you believe your merchant account has been accessed without authorization. Report merchants or users violating CeyPay's terms of service. ## How to Report Collect all relevant details about the incident: * Transaction IDs * Timestamps * Merchant or customer information * Screenshots or evidence * Description of the issue Email our security team at **[support@ceypay.io](mailto:support@ceypay.io)** with the subject line "ABUSE REPORT". Include all gathered information in your email. Our security team will acknowledge your report within 24 hours and provide a case reference number. We'll investigate the matter and take appropriate action. We may contact you for additional information. **Urgent Security Issues**: If you believe there's an immediate security threat or ongoing fraud, call our emergency hotline for immediate assistance. ## What Happens Next? 1. **Acknowledgment**: We'll confirm receipt of your report within 24 hours 2. **Investigation**: Our security team reviews the incident 3. **Action**: Appropriate measures are taken (account suspension, law enforcement notification, etc.) 4. **Resolution**: You'll be notified of the outcome ## Types of Abuse ### Merchant Abuse * Selling prohibited goods or services * Processing fraudulent transactions * Chargebacks fraud * Money laundering activities ### Customer Abuse * Stolen payment credentials * Unauthorized access attempts * Social engineering attacks * Chargeback abuse ### Technical Abuse * API abuse or rate limiting violations * Webhook manipulation * System penetration attempts * Data scraping ## Privacy & Confidentiality All abuse reports are handled with strict confidentiality. Your identity will be protected, and information will only be shared as necessary for investigation or with law enforcement if required. ## Contact Information **Email**: [support@ceypay.io](mailto:support@ceypay.io) **Subject**: ABUSE REPORT - \[Brief Description] **Response Time**: Within 24 hours For general support inquiries unrelated to abuse or security, please contact **[support@ceypay.io](mailto:support@ceypay.io)** instead. ## Prevention Tips * Use strong, unique passwords * Enable two-factor authentication * Never share your API keys or webhook secrets * Regularly review your transaction logs * CeyPay will never ask for your password via email - Always check sender email addresses carefully - Visit ceypay.io directly instead of clicking email links - Report suspicious emails immediately * Set up transaction alerts * Review your dashboard regularly * Report any unfamiliar transactions immediately * Keep records of all settlements ## Legal Disclaimer CeyPay cooperates fully with law enforcement agencies. Fraudulent activity may be reported to appropriate authorities and could result in criminal prosecution. # Support Source: https://docs.ceypay.io/support Get help with CeyPay integration, troubleshooting, and technical support. This page outlines CeyPay's support channels, available resources, and policies. Learn how to access support based on your needs. ## Support Channels The support channels available to you depend on your usage of CeyPay. | Support Channels | All Merchants | | :------------------------------------------------------------------- | :-----------: | | [Email](#email-support) | ✓ | | [Documentation](#documentation) | ✓ | | [Telegram Discussion](#telegram-discussion) | ✓ | ## Email Support **Primary support email:** [support@ceypay.io](mailto:support@ceypay.io) ### What to Include To help us resolve your issue quickly, please include: 1. **Merchant ID** (if applicable) 2. **Error messages** or screenshots 3. **Steps to reproduce** the issue 4. **Expected vs actual** behavior 5. **Environment**: WordPress version, PHP version, plugin version ## Documentation Comprehensive documentation is available for all users: Installation, configuration, and troubleshooting guides Complete API documentation with interactive playground Step-by-step integration guides and best practices Common questions and answers ## GitHub Report bugs and request features on GitHub: * **Bug Reports**: [Create an issue](https://github.com/CeyPay-io/woocommerce-plugin/issues) * **Security Issues**: Email [support@ceypay.io](mailto:support@ceypay.io) directly **Do not** post sensitive information (API keys, merchant IDs, customer data) in public GitHub issues. ## WooCommerce Plugin Download the latest version of the CeyPay WooCommerce plugin to start accepting crypto payments on your WordPress store. For installation instructions and configuration guides, visit the [WordPress Plugin Documentation](/integrations/wordpress/introduction). ## Telegram Discussion Join our community discussion group for real-time support and discussions. The place to talk all things CeyPay: * **Crypto in → LKR out discussions** — Payment flows, conversion rates, and settlement * **Merchant onboarding, use cases, and feedback** — Share experiences and best practices * **Product updates, bugs, and feature ideas** — Stay updated and help shape the product * **Payments, regulation, and real-world adoption** — Industry insights and compliance For sensitive issues (API keys, security concerns), please use email support instead. ## Self-Service Resources ### Troubleshooting Guides Before contacting support, check our troubleshooting resources: * [WordPress Plugin Troubleshooting](/integrations/wordpress/troubleshooting) * [API Error Codes](/api/v1/errors) * [Webhook Integration Guide](/api/v1/webhooks) * [Rate Limiting](/api/v1/rate-limits) ### Developer Tools * **API Playground**: Test endpoints directly in the [API Reference](/api/v1/quickstart) * **Test Mode**: Simulate payments without real transactions * **Webhook Logs**: View webhook delivery history in merchant dashboard * **Audit Logs**: Track API key usage and changes ## Changes to Support Policy We reserve the right to modify this support policy at any time. Changes will be effective immediately upon posting. Continued use of CeyPay services after modifications constitutes acceptance of the updated policy. **Last updated:** February 5, 2026 *** ## Quick Links [support@ceypay.io](mailto:support@ceypay.io) GitHub Issues Browse Docs