# DPT Merchant API — Complete Reference # Generated: 2026-03-16T07:43:57.428Z # Source: https://docs.dpt.xyz # # This file contains the full DPT Merchant API documentation in a single # plain-text file for use as LLM context. Paste the contents below into # your AI assistant to get accurate, up-to-date API help. # # Production: https://api.dpt.xyz/v1 # Test: https://api-test.dpt.xyz/v1 # Auth: Authorization: Bearer dptb_ # Amounts: smallest currency unit (USDC: micro-units, USD: cents) # ───────────────────────────────────────────────────────────────────────────── # Merchant API — Overview The Merchant API lets your backend server interact with DPT programmatically using an **API key** instead of a user session. Use it to automate payments, manage customers, run payroll, and issue cards to your users. ## Base URLs | Environment | Base URL | |-------------|----------| | **Production** | `https://api.dpt.xyz` | | **Test** | `https://api-test.dpt.xyz` | Use the test environment for development and integration testing — it runs against a sandbox with no real funds. Switch to production when you're ready to go live. ## Authentication Every request must include your API key in the `Authorization` header: ```http Authorization: Bearer dptb_your_api_key_here ``` API keys are scoped to a single business. You do not need to pass a business ID in the URL — it is derived from the key itself. ### Getting an API Key 1. Go to **Dashboard → API Keys** 2. Click **Create API Key**, give it a name 3. Copy the key immediately — it is shown **once only** Keys are prefixed with `dptb_`. Store them securely (environment variables, secret managers). Never commit keys to source control. ### Revoking a Key From the dashboard, navigate to **API Keys** and click the revoke button next to any key. Revocation takes effect immediately. ## Request Format - All request bodies must be JSON - Set `Content-Type: application/json` on requests with a body ```http POST /v1/checkouts HTTP/1.1 Authorization: Bearer dptb_... Content-Type: application/json { "title": "Order #1234", "amount": 10000, "currency": "USDC" } ``` ## Response Format All responses are JSON. Successful responses return the created or fetched resource directly. Errors return: ```json { "error": "Human-readable error message" } ``` ## HTTP Status Codes | Code | Meaning | |------|---------| | `200` | Success with body | | `204` | Success, no body (DELETE, some POST) | | `400` | Bad request — check your parameters | | `401` | Missing or invalid API key | | `403` | API key does not have access to the resource | | `404` | Resource not found | | `422` | Validation error | | `500` | Server error | ## Amounts and Currencies All monetary amounts are in **the smallest unit** of the currency: - USDC / USDT: amounts are in micro-units (1 USDC = 1,000,000) - USD (fiat): amounts are in cents (1 USD = 100) Currency codes follow ISO 4217 for fiat and ticker symbols for crypto (e.g. `USDC`, `USDT`, `ETH`). ## Rate Limits - 1000 requests per minute per API key - 10 concurrent requests per API key Exceeding limits returns `429 Too Many Requests`. ## Available Resource Groups | Doc | Resources | |-----|-----------| | [Checkouts](./checkouts) | Payment links, refunds | | [Invoices](./invoices) | Invoice creation and lifecycle | | [Customers](./customers) | Customer records | | [Payroll](./payroll) | Payroll runs and disbursements | | [Cardholders](./cardholders) | Managed users and card issuance | | [Webhooks](./webhooks) | Event delivery and security | ─────────────────────────────────────────────────────────────────────────────── # Checkouts A checkout is a payment link you send to a customer. Once paid, a `checkout.paid` webhook is fired. ## Object ```json { "id": "uuid", "title": "Order #1234", "description": "Premium plan — 1 year", "amount": 10000000, "currency": "USDC", "method": "crypto", "status": "active", "hosted_url": "https://pay.dpt.com/c/abc123", "expires_at": "2026-04-01T00:00:00Z", "created_at": "2026-03-16T10:00:00Z" } ``` **Status values:** `active` | `paid` | `expired` | `cancelled` --- ## Create Checkout ```http POST /v1/checkouts ``` ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | ✓ | Short label shown on the payment page | | `description` | string | | Extended description | | `amount` | integer | | Amount in smallest unit. Omit for open-amount checkouts | | `currency` | string | | `USDC`, `USDT`, `USD`, etc. Default: `USDC` | | `method` | string | | `crypto` or `fiat`. Default: `crypto` | | `customer_id` | uuid | | Attach an existing customer | | `customer_name` | string | | Create/attach customer inline | | `customer_email` | string | | | | `customer_country` | string | | ISO 3166-1 alpha-2 (e.g. `US`) | ### Example ```bash curl -X POST https://api-test.dpt.xyz/v1/checkouts \ -H "Authorization: Bearer dptb_..." \ -H "Content-Type: application/json" \ -d '{ "title": "Order #1234", "amount": 50000000, "currency": "USDC", "customer_email": "alice@example.com" }' ``` ### Response `200` Returns the created [Checkout object](#object). --- ## List Checkouts ```http GET /v1/checkouts ``` Returns an array of checkout objects ordered by creation date (newest first). --- ## Get Checkout ```http GET /v1/checkouts/{id} ``` --- ## Cancel Checkout ```http DELETE /v1/checkouts/{id} ``` Cancels an `active` checkout. Returns `204 No Content`. --- ## Refund Checkout ```http POST /v1/checkouts/{id}/refund ``` ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `amount` | integer | | Partial refund amount. Omit to refund in full | | `reason` | string | | Optional reason memo | Returns `204 No Content`. ─────────────────────────────────────────────────────────────────────────────── # Invoices Invoices are drafted, sent to customers, and paid via a checkout link. An invoice goes through a defined lifecycle: `draft → sent → paid | cancelled`. ## Object ```json { "id": "uuid", "number": "INV-0042", "status": "draft", "customer_id": "uuid", "customer_name": "Acme Corp", "customer_email": "billing@acme.com", "line_items": [ { "description": "Consulting — March", "quantity": 1, "unit_price": 200000000, "total": 200000000 } ], "subtotal": 200000000, "tax_rate": 0, "tax_amount": 0, "total": 200000000, "currency": "USDC", "due_date": "2026-04-15", "created_at": "2026-03-16T10:00:00Z" } ``` **Status values:** `draft` | `sent` | `paid` | `cancelled` --- ## Create Invoice ```http POST /v1/invoices ``` ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `customer_id` | uuid | | Attach existing customer | | `customer_name` | string | | Inline customer name | | `customer_email` | string | | | | `customer_country` | string | | | | `currency` | string | | Default: `USDC` | | `line_items` | array | ✓ | See below | | `due_date` | string | | ISO 8601 date `YYYY-MM-DD` | | `tax_rate` | number | | Applied to subtotal, e.g. `0.1` for 10% | **Line item fields:** | Field | Type | Required | |-------|------|----------| | `description` | string | ✓ | | `quantity` | integer | ✓ | | `unit_price` | integer | ✓ | ### Example ```bash curl -X POST https://api-test.dpt.xyz/v1/invoices \ -H "Authorization: Bearer dptb_..." \ -H "Content-Type: application/json" \ -d '{ "customer_email": "billing@acme.com", "customer_name": "Acme Corp", "currency": "USDC", "due_date": "2026-04-15", "line_items": [ { "description": "Consulting — March", "quantity": 1, "unit_price": 200000000 } ] }' ``` --- ## List Invoices ```http GET /v1/invoices ``` --- ## Get Invoice ```http GET /v1/invoices/{id} ``` --- ## Activate Invoice (Send to Customer) Transitions from `draft` to `sent` and generates a hosted checkout link. ```http POST /v1/invoices/{id}/activate ``` ### Body (optional) | Field | Type | Description | |-------|------|-------------| | `method` | string | `crypto` (default) or `fiat` | | `send_email` | bool | Send email to customer. Default: `true` | ### Response `200` ```json { "invoice": { /* Invoice object */ }, "checkout": { /* Checkout object with hosted_url */ } } ``` --- ## Cancel Invoice ```http DELETE /v1/invoices/{id} ``` Only `draft` or `sent` invoices can be cancelled. Returns `204 No Content`. ─────────────────────────────────────────────────────────────────────────────── # Customers Customer records are reusable — attach them to checkouts and invoices instead of repeating name/email each time. ## Object ```json { "id": "uuid", "name": "Alice Smith", "email": "alice@example.com", "phone": "+1 415 000 0000", "country": "US", "created_at": "2026-03-16T10:00:00Z" } ``` --- ## Create Customer ```http POST /v1/customers ``` ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | ✓ | Full name | | `email` | string | ✓ | | | `phone` | string | | E.164 format recommended | | `country` | string | | ISO 3166-1 alpha-2 (e.g. `US`) | ### Example ```bash curl -X POST https://api-test.dpt.xyz/v1/customers \ -H "Authorization: Bearer dptb_..." \ -H "Content-Type: application/json" \ -d '{ "name": "Alice Smith", "email": "alice@example.com", "country": "US" }' ``` --- ## List Customers ```http GET /v1/customers ``` --- ## Get Customer ```http GET /v1/customers/{id} ``` --- ## Update Customer ```http PUT /v1/customers/{id} ``` Send only the fields you want to change. All fields are optional. --- ## Delete Customer ```http DELETE /v1/customers/{id} ``` Returns `204 No Content`. Existing checkouts and invoices referencing this customer are unaffected. ─────────────────────────────────────────────────────────────────────────────── # Payroll Payroll lets you batch-disburse funds to multiple recipients (employees or contractors) in a single run. Funds are sent from your business wallet. ## Objects ### PayrollRun ```json { "id": "uuid", "title": "March 2026 Payroll", "status": "draft", "currency": "USDC", "total_amount": 0, "items": [], "created_at": "2026-03-16T10:00:00Z" } ``` **Status values:** `draft` | `executed` ### PayrollItem ```json { "id": "uuid", "run_id": "uuid", "wallet_address": "0xabc...", "amount": 5000000000, "currency": "USDC", "note": "March salary" } ``` --- ## Workflow ``` 1. Create a run (draft) 2. Add one or more items (recipients) 3. Execute the run — all transfers happen atomically ``` --- ## Create Payroll Run ```http POST /v1/payroll ``` ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | ✓ | Label for this run, e.g. `"March 2026"` | | `currency` | string | | Default: `USDC` | ### Example ```bash curl -X POST https://api-test.dpt.xyz/v1/payroll \ -H "Authorization: Bearer dptb_..." \ -H "Content-Type: application/json" \ -d '{ "title": "March 2026 Payroll", "currency": "USDC" }' ``` --- ## List Payroll Runs ```http GET /v1/payroll ``` --- ## Add Employee to Run ```http POST /v1/payroll/{run_id}/items ``` Can be called multiple times before execution. ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `wallet_address` | string | ✓ | Recipient crypto address | | `amount` | integer | ✓ | Amount in smallest unit | | `note` | string | | Memo / employee name | ### Example ```bash curl -X POST https://api-test.dpt.xyz/v1/payroll/RUN_ID/items \ -H "Authorization: Bearer dptb_..." \ -H "Content-Type: application/json" \ -d '{ "wallet_address": "0xabc123...", "amount": 5000000000, "note": "Alice — March salary" }' ``` --- ## Execute Payroll Run Sends all funds. This action is **irreversible**. ```http POST /v1/payroll/{run_id}/execute ``` Returns `204 No Content`. The run status changes to `executed` and all transfers are processed. Your business wallet must have sufficient balance across all items. ─────────────────────────────────────────────────────────────────────────────── # Cardholders Cardholders are managed users your business creates and controls. You can issue physical or virtual cards to them, fund their wallets, and monitor spending — all programmatically. ## Full Lifecycle ``` 1. Create cardholder 2. Complete KYC (via SumSub token) 3. Issue a card 4. Fund the cardholder's wallet 5. Manage card — freeze/unfreeze, set limits, view secrets 6. Monitor transactions 7. Recall unused funds ``` --- ## Cardholder Object ```json { "id": "uuid", "business_id": "uuid", "name": "Bob Jones", "email": "bob@yourcompany.com", "username": "bob_abc123", "is_kyc_verified": false, "created_at": "2026-03-16T10:00:00Z" } ``` ## Card Object ```json { "id": "uuid", "user_id": "uuid", "catalogue_id": "uuid", "status": "active", "ctype": "virtual", "last4": "4242", "exp_month": "03", "exp_year": "2029", "spend_tx_max": 100000, "spend_daily_max": 500000, "spend_monthly_max": 2000000, "spend_online_enabled": true, "spend_present_enabled": false, "created_at": "2026-03-16T10:00:00Z" } ``` **Card status values:** `notActivated` | `active` | `locked` | `canceled` --- ## Step 1 — Create Cardholder ```http POST /v1/cardholders ``` ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | ✓ | Full name | | `email` | string | | Email address | ### Example ```bash curl -X POST https://api-test.dpt.xyz/v1/cardholders \ -H "Authorization: Bearer dptb_..." \ -H "Content-Type: application/json" \ -d '{ "name": "Bob Jones", "email": "bob@yourcompany.com" }' ``` ### Response `200` Returns the [Cardholder object](#cardholder-object). Note the `id` — you will need it for all subsequent calls. --- ## Step 2 — Start KYC Returns a SumSub SDK token. Embed the [SumSub Web SDK](https://docs.sumsub.com/docs/web-sdk) in your app and pass the token to collect the cardholder's identity documents. ```http POST /v1/cardholders/{id}/kyc ``` ### Response `200` ```json { "token": "sumsub_sdk_token_..." } ``` After the cardholder completes verification, `is_kyc_verified` becomes `true`. KYC status is updated automatically via webhook from SumSub — no polling needed. --- ## Step 3 — Issue a Card The cardholder must be KYC-verified before a card can be issued. ```http POST /v1/cardholders/{id}/cards ``` ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `catalogue` | uuid | ✓ | Card product ID (get from dashboard) | | `type` | string | ✓ | `virtual` or `physical` | ### Example ```bash curl -X POST https://api-test.dpt.xyz/v1/cardholders/CH_ID/cards \ -H "Authorization: Bearer dptb_..." \ -H "Content-Type: application/json" \ -d '{ "catalogue": "CATALOGUE_UUID", "type": "virtual" }' ``` --- ## Step 4 — Fund Cardholder Transfer funds from your business wallet to the cardholder's wallet. ```http POST /v1/cardholders/{id}/fund ``` ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `amount` | integer | ✓ | Amount in smallest unit | | `currency` | string | ✓ | e.g. `USDC` | | `remark` | string | | Optional memo | ### Example ```bash curl -X POST https://api-test.dpt.xyz/v1/cardholders/CH_ID/fund \ -H "Authorization: Bearer dptb_..." \ -H "Content-Type: application/json" \ -d '{ "amount": 1000000000, "currency": "USDC" }' ``` Returns `204 No Content`. --- ## List Cardholders ```http GET /v1/cardholders ``` --- ## Get Cardholder ```http GET /v1/cardholders/{id} ``` --- ## Recall Funds Pull unused funds back from a cardholder's wallet to your business wallet. ```http POST /v1/cardholders/{id}/recall ``` Same body as [Fund Cardholder](#step-4--fund-cardholder). Returns `204 No Content`. --- ## List Cardholder Cards ```http GET /v1/cardholders/{id}/cards ``` --- ## Get Card ```http GET /v1/cardholders/{ch_id}/cards/{card_id} ``` --- ## Update Card (Freeze / Unfreeze) ```http PATCH /v1/cardholders/{ch_id}/cards/{card_id} ``` ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `status` | string | ✓ | `active` to unfreeze, `locked` to freeze | --- ## Update Card Spending Limits ```http PUT /v1/cardholders/{ch_id}/cards/{card_id}/limits ``` ### Body | Field | Type | Description | |-------|------|-------------| | `spend_tx_max` | integer | Max per-transaction amount (cents/micro-units) | | `spend_daily_max` | integer | Max daily spend | | `spend_monthly_max` | integer | Max monthly spend | | `spend_online_enabled` | bool | Allow online / card-not-present transactions | | `spend_present_enabled` | bool | Allow in-person / contactless transactions | --- ## Get Card Secrets (PAN + CVV) Returns the full card number and CVV, encrypted with Rain's public key. You must use the [Rain Secure SDK](https://docs.rain.com) to decrypt and display these to the cardholder. ```http POST /v1/cardholders/{ch_id}/cards/{card_id}/secrets ``` ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `session` | string | ✓ | Session token from Rain Secure SDK | | `encrypted_data` | object | | Additional encrypted payload if required | ### Response `200` ```json { "encryptedCvc": { "iv": "...", "data": "..." }, "encryptedPan": { "iv": "...", "data": "..." } } ``` --- ## Get Card PIN ```http GET /v1/cardholders/{ch_id}/cards/{card_id}/pin ``` Same request body as [Get Secrets](#get-card-secrets-pan--cvv). ### Response `200` ```json { "encryptedPin": { "iv": "...", "data": "..." } } ``` --- ## Set Card PIN ```http PUT /v1/cardholders/{ch_id}/cards/{card_id}/pin ``` ### Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `session` | string | ✓ | Session token from Rain Secure SDK | | `encrypted_data` | object | ✓ | New PIN encrypted with Rain's public key | Returns `200 OK`. --- ## List Card Transactions ```http GET /v1/cardholders/{ch_id}/cards/{card_id}/transactions ``` Returns the most recent card transactions for this card. ### Response `200` ```json [ { "id": "uuid", "amount": -50000, "currency": "USDC", "description": "Starbucks #1234", "status": "completed", "created_at": "2026-03-16T09:00:00Z" } ] ``` ─────────────────────────────────────────────────────────────────────────────── # Webhooks Webhooks let DPT notify your server in real time when events happen in your business. Instead of polling the API, you register an endpoint and DPT sends an HTTP POST to it whenever an event fires. ## Setup 1. Go to **Dashboard → Webhooks** 2. Click **Add Webhook** 3. Enter your endpoint URL and select which events to subscribe to 4. After creation, copy the **signing secret** — it is shown **once only** --- ## Event Types | Event | Fires when | |-------|-----------| | `checkout.paid` | A checkout is successfully paid | | `checkout.expired` | A checkout reaches its expiry time unpaid | | `checkout.cancelled` | A checkout is cancelled via the API or dashboard | | `invoice.paid` | An invoice is fully paid | | `invoice.sent` | An invoice is activated and sent to the customer | | `payroll.executed` | A payroll run completes all disbursements | | `payout.completed` | A payout to an external account is confirmed | | `payout.failed` | A payout fails | --- ## Payload All webhook deliveries share the same envelope: ```json { "event": "checkout.paid", "data": { "id": "uuid", "title": "Order #1234", "amount": 50000000, "currency": "USDC", "status": "paid", ... } } ``` The `data` object contains the full resource at the time of the event. --- ## Request Headers | Header | Value | |--------|-------| | `Content-Type` | `application/json` | | `X-DPT-Event` | Event type, e.g. `checkout.paid` | | `X-DPT-Signature` | `sha256={hex_signature}` | --- ## Verifying the Signature Every delivery is signed with HMAC-SHA256 using your webhook's signing secret. **Always verify the signature** before processing the event. ### Algorithm 1. Read the raw request body as a string (do not parse it first) 2. Compute `HMAC-SHA256(secret, raw_body)` and hex-encode the result 3. Compare with the value after `sha256=` in `X-DPT-Signature` 4. If they match, the payload is authentic ### Examples **Node.js** ```js const crypto = require('crypto'); function verifySignature(secret, rawBody, signatureHeader) { const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signatureHeader) ); } // Express handler app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['x-dpt-signature']; if (!verifySignature(process.env.WEBHOOK_SECRET, req.body, sig)) { return res.status(401).send('Invalid signature'); } const { event, data } = JSON.parse(req.body); // process event... res.sendStatus(200); }); ``` **Python** ```python import hmac, hashlib def verify_signature(secret: str, raw_body: bytes, signature_header: str) -> bool: expected = 'sha256=' + hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature_header) # Flask handler @app.route('/webhooks', methods=['POST']) def webhook(): sig = request.headers.get('X-DPT-Signature', '') if not verify_signature(os.environ['WEBHOOK_SECRET'], request.data, sig): abort(401) payload = request.get_json() event = payload['event'] data = payload['data'] # process event... return '', 200 ``` **Go** ```go func verifySignature(secret, rawBody, signatureHeader string) bool { mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(rawBody)) expected := "sha256=" + hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(signatureHeader)) } ``` --- ## Retry Policy If your endpoint returns a non-2xx HTTP status (or times out after 10 seconds), DPT retries the delivery with exponential backoff: | Attempt | Delay after previous failure | |---------|------------------------------| | 1 | Immediate | | 2 | 5 minutes | | 3 | 30 minutes | | 4 | 2 hours | | 5 | 5 hours | After 5 failed attempts, the delivery is marked **failed** and no further retries occur. You can view the full delivery history, including HTTP status codes and response bodies, in **Dashboard → Webhooks**. --- ## Delivery History Each event delivery is logged with: - **Status**: `pending`, `delivered`, `failed` - **Attempt number** - **HTTP response status** from your server - **Timestamp** Access the log from the dashboard by clicking on any webhook endpoint. --- ## Best Practices - **Return 2xx quickly** — do heavy processing asynchronously (queue the event and acknowledge immediately) - **Handle duplicates** — use `data.id` to deduplicate in case of retries - **Check the event type first** — ignore events your code doesn't handle - **Use HTTPS** — only register endpoints with valid TLS certificates - **Rotate secrets periodically** — delete and recreate the webhook to get a new secret