Introduction
The DePix App API lets you create and manage Pix checkouts programmatically. It is the same API that powers the BTCPay Server plugin and the Merchant Dashboard.
Base URL
https://depix-backend.vercel.app
Format
All requests and responses use JSON (Content-Type: application/json). Monetary values are always in centavos (integers). Example: R$ 10.00 = 1000.
Authentication
Use an API key in the Authorization header on all authenticated requests.
Authorization: Bearer sk_live_<your-key>
Key types
| Prefix | Type | Behavior |
|---|---|---|
| sk_live_ | Live | Real checkouts. Real money. |
| sk_test_ | Test | Test checkouts. No real money is moved. |
To manage your keys (create, list, revoke), go to the Merchant Dashboard at depixapp.com/#merchant. Maximum of 5 live keys and 5 test keys active per account.
Errors
Errors follow the format below. The errorMessage field is always human-readable.
{
"response": {
"errorMessage": "Invalid data.",
"errors": [ // present on validation errors
{ "field": "amount", "message": "Required." }
]
}
}
| Status | Meaning |
|---|---|
| 400 | Invalid data — check the fields. |
| 401 | API key missing, invalid, or revoked. |
| 403 | Permission denied. Merchant account not set up, or key lacks access to the resource. |
| 404 | Resource not found. |
| 409 | State conflict — operation not allowed in the current status. |
| 429 | Rate limit exceeded. Wait and try again. |
| 500 | Internal server error. Try again in a few moments. |
| 503 | Service unavailable — platform under maintenance or Pix provider temporarily down. |
Create checkout
Creates a new Pix checkout. Returns the QR code and the payment URL to display to your customer.
Parameters
| Field | Type | Description | |
|---|---|---|---|
| amount | integer | required | Amount in centavos. Minimum: 500 (R$ 5.00). Maximum: 300000 (R$ 3,000.00). |
| description | string | optional | Order description. Maximum 500 characters. Displayed on the payment page. |
| expires_in | integer | optional | Expiration time in seconds. Default: 1200 (20min). Minimum: 300 (5min). Maximum: 1200 (20min). |
| image_url | string | optional | HTTPS URL of the product image. Displayed on the payment page. |
| callback_url | string | optional | HTTPS URL that receives the checkout webhooks. |
| redirect_url | string | optional | URL to redirect the customer after payment. |
| metadata | object | optional | Additional data from your system (order_id, user_id, etc.). Maximum 4KB. Returned in webhooks. |
Example
curl -X POST https://depix-backend.vercel.app/api/checkouts \ -H "Authorization: Bearer sk_live_<your-key>" \ -H "Content-Type: application/json" \ -d '{ "amount": 2990, "description": "T-shirt size M", "expires_in": 900, "callback_url": "https://my-store.com/webhook/depix", "metadata": { "order_id": "ORD-123" } }'
const res = await fetch("https://depix-backend.vercel.app/api/checkouts", { method: "POST", headers: { "Authorization": "Bearer sk_live_<your-key>", "Content-Type": "application/json", }, body: JSON.stringify({ amount: 2990, description: "T-shirt size M", expires_in: 900, callback_url: "https://my-store.com/webhook/depix", metadata: { order_id: "ORD-123" }, }), }); const data = await res.json(); console.log(data.id, data.payment_url);
import requests resp = requests.post( "https://depix-backend.vercel.app/api/checkouts", headers={"Authorization": "Bearer sk_live_<your-key>"}, json={ "amount": 2990, "description": "T-shirt size M", "expires_in": 900, "callback_url": "https://my-store.com/webhook/depix", "metadata": {"order_id": "ORD-123"}, }, ) data = resp.json() print(data["id"], data["payment_url"])
$ch = curl_init("https://depix-backend.vercel.app/api/checkouts"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer sk_live_<your-key>", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "amount" => 2990, "description" => "T-shirt size M", "expires_in" => 900, "callback_url" => "https://my-store.com/webhook/depix", "metadata" => ["order_id" => "ORD-123"], ]), ]); $response = curl_exec($ch); $data = json_decode($response, true); echo $data["id"] . " " . $data["payment_url"];
using var client = new HttpClient(); client.DefaultRequestHeaders.Add("Authorization", "Bearer sk_live_<your-key>"); var payload = new { amount = 2990, description = "T-shirt size M", expires_in = 900, callback_url = "https://my-store.com/webhook/depix", metadata = new { order_id = "ORD-123" } }; var res = await client.PostAsync( "https://depix-backend.vercel.app/api/checkouts", new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json") ); var json = await res.Content.ReadAsStringAsync(); Console.WriteLine(json);
body := `{"amount":2990,"description":"T-shirt size M","expires_in":900,"callback_url":"https://my-store.com/webhook/depix","metadata":{"order_id":"ORD-123"}}` req, _ := http.NewRequest("POST", "https://depix-backend.vercel.app/api/checkouts", strings.NewReader(body)) req.Header.Set("Authorization", "Bearer sk_live_<your-key>") req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() io.Copy(os.Stdout, resp.Body)
require "net/http" require "json" uri = URI("https://depix-backend.vercel.app/api/checkouts") req = Net::HTTP::Post.new(uri, { "Authorization" => "Bearer sk_live_<your-key>", "Content-Type" => "application/json", }) req.body = { amount: 2990, description: "T-shirt size M", expires_in: 900, callback_url: "https://my-store.com/webhook/depix", metadata: { order_id: "ORD-123" } }.to_json res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } puts JSON.parse(res.body)
HttpClient client = HttpClient.newHttpClient(); String json = """ {"amount":2990,"description":"T-shirt size M","expires_in":900, "callback_url":"https://my-store.com/webhook/depix", "metadata":{"order_id":"ORD-123"}}"""; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://depix-backend.vercel.app/api/checkouts")) .header("Authorization", "Bearer sk_live_<your-key>") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());
{
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "pending",
"amount": 2990,
"description": "T-shirt size M",
"image_url": null,
"expires_at": "2025-06-01T15:30:00.000Z",
"is_live": true,
"payment_url": "https://pay.depixapp.com/chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"pix": {
"qr_code": "00020126580014br.gov.bcb.pix..." // EMV payload for QR code
}
}
payment_url to your customer or generate a QR code from pix.qr_code. The QR code is compatible with any banking app.
Get checkout
Returns the details of a specific checkout.
curl https://depix-backend.vercel.app/api/checkouts/chk_01jxxxxxxxxxxxxxxxxxxxxxx \ -H "Authorization: Bearer sk_live_<your-key>"
{
"checkout": {
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "completed", // pending | processing | completed | cancelled | expired
"amount": 2990,
"description": "T-shirt size M",
"image_url": null,
"callback_url": "https://my-store.com/webhook/depix",
"redirect_url": null,
"metadata": { "order_id": "ORD-123" },
"expires_at": "2025-06-01T15:30:00.000Z",
"is_live": true,
"created_at": "2025-06-01T15:00:00.000Z",
"processing_at": "2025-06-01T15:02:00.000Z",
"completed_at": "2025-06-01T15:22:00.000Z",
"cancelled_at": null,
"blockchain_tx_id": "abc123...def456" // Liquid txid (present when completed)
}
}
Possible statuses
| Status | Meaning |
|---|---|
| pending | Awaiting payment. |
| processing | Pix received, processing conversion to DePix. |
| completed | Payment confirmed. DePix in the merchant's wallet. |
| cancelled | Cancelled by the merchant. |
| expired | Payment deadline expired. |
List checkouts
Lists the merchant's checkouts with filters and pagination.
Query params (all optional)
| Parameter | Description |
|---|---|
| status | Filter by status: pending, completed, cancelled, expired. |
| product_id | Filter by product. E.g.: prd_xxx. |
| from | Start date (ISO 8601). E.g.: 2025-06-01T00:00:00Z. |
| to | End date (ISO 8601). |
| q | Search by ID or description. |
| limit | Number of results per page. Default: 50. Maximum: 100. |
| offset | Pagination. Default: 0. |
curl "https://depix-backend.vercel.app/api/checkouts?status=completed&limit=20" \ -H "Authorization: Bearer sk_live_<your-key>"
{
"checkouts": [
{
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "completed",
"amount": 2990,
"description": "T-shirt size M",
"product_name": "Black T-shirt", // null if checkout is not linked to a product
"metadata": "{\"order_id\":\"42\"}", // JSON string, null if absent
"created_at": "2025-06-01T15:00:00.000Z",
"processing_at": "2025-06-01T15:02:14.000Z",
"expires_at": "2025-06-01T15:30:00.000Z",
"is_live": true
}
],
"stats": {
"total": 47,
"pending": 2,
"completed": 40,
"completed_amount": 189500 // centavos — R$ 1,895.00
},
"limit": 20,
"offset": 0
}
Cancel checkout
Cancels a pending checkout. Only checkouts with pending status can be cancelled.
curl -X POST https://depix-backend.vercel.app/api/checkouts/chk_01jxxxxxxxxxxxxxxxxxxxxxx/cancel \ -H "Authorization: Bearer sk_live_<your-key>"
{ "success": true }
Create product
Creates a new product with a fixed price. Each product generates a permanent payment link that can be shared with your customers.
Parameters
| Field | Type | Description | |
|---|---|---|---|
| name | string | required | Product name shown in the UI. 2-80 characters. |
| slug | string | optional | URL identifier. If omitted, auto-generated from name. Lowercase letters, numbers, and hyphens. 2-60 characters. Cannot start/end with a hyphen. |
| amount | integer | required | Amount in centavos. Minimum: 500. Maximum: 300000. |
| description | string | optional | Product description. Maximum 500 characters. |
| image_url | string | optional | HTTPS URL of the product image. |
| callback_url | string | optional | HTTPS URL for webhooks. Overrides the merchant default. |
| redirect_url | string | optional | Redirect URL. Overrides the merchant default. |
| metadata | object | optional | Additional data. Maximum 4KB. Included in webhooks for generated checkouts. |
| expires_in | integer | optional | Checkout expiration time in seconds. Default: 1200 (20min). Minimum: 300 (5min). Maximum: 1200 (20min). |
Example
curl -X POST https://depix-backend.vercel.app/api/products \ -H "Authorization: Bearer sk_live_<your-key>" \ -H "Content-Type: application/json" \ -d '{ "name": "T-shirt M", "slug": "tshirt-m", "amount": 2990, "description": "T-shirt size M" }'
const res = await fetch("https://depix-backend.vercel.app/api/products", { method: "POST", headers: { "Authorization": "Bearer sk_live_<your-key>", "Content-Type": "application/json", }, body: JSON.stringify({ name: "T-shirt M", slug: "tshirt-m", amount: 2990, description: "T-shirt size M", }), }); const data = await res.json(); console.log(data.product.payment_url);
import requests resp = requests.post( "https://depix-backend.vercel.app/api/products", headers={"Authorization": "Bearer sk_live_<your-key>"}, json={ "name": "T-shirt M", "slug": "tshirt-m", "amount": 2990, "description": "T-shirt size M", }, ) data = resp.json() print(data["product"]["payment_url"])
$ch = curl_init("https://depix-backend.vercel.app/api/products"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer sk_live_<your-key>", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "name" => "T-shirt M", "slug" => "tshirt-m", "amount" => 2990, "description" => "T-shirt size M", ]), ]); $response = curl_exec($ch); $data = json_decode($response, true); echo $data["product"]["payment_url"];
using var client = new HttpClient(); client.DefaultRequestHeaders.Add("Authorization", "Bearer sk_live_<your-key>"); var payload = new { name = "T-shirt M", slug = "tshirt-m", amount = 2990, description = "T-shirt size M" }; var res = await client.PostAsync( "https://depix-backend.vercel.app/api/products", new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json") ); Console.WriteLine(await res.Content.ReadAsStringAsync());
body := `{"name":"T-shirt M","slug":"tshirt-m","amount":2990,"description":"T-shirt size M"}` req, _ := http.NewRequest("POST", "https://depix-backend.vercel.app/api/products", strings.NewReader(body)) req.Header.Set("Authorization", "Bearer sk_live_<your-key>") req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() io.Copy(os.Stdout, resp.Body)
require "net/http" require "json" uri = URI("https://depix-backend.vercel.app/api/products") req = Net::HTTP::Post.new(uri, { "Authorization" => "Bearer sk_live_<your-key>", "Content-Type" => "application/json", }) req.body = { name: "T-shirt M", slug: "tshirt-m", amount: 2990, description: "T-shirt size M" }.to_json res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } puts JSON.parse(res.body)
HttpClient client = HttpClient.newHttpClient(); String json = """ {"name":"T-shirt M","slug":"tshirt-m","amount":2990,"description":"T-shirt size M"}"""; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://depix-backend.vercel.app/api/products")) .header("Authorization", "Bearer sk_live_<your-key>") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());
{
"product": {
"id": "prd_xxx",
"name": "T-shirt M",
"slug": "tshirt-m",
"amount": 2990,
"description": "T-shirt size M",
"image_url": null,
"callback_url": null,
"redirect_url": null,
"metadata": null,
"expires_in": 1200,
"active": true,
"is_live": true,
"payment_url": "https://pay.depixapp.com/joao/tshirt-m"
}
}
List products
Lists the merchant's products with filters and pagination.
Query params (all optional)
| Parameter | Description |
|---|---|
| active | Filter by status: 1 (active) or 0 (inactive). |
| q | Search by name, slug, or description. |
| limit | Number of results. Default: 50. Maximum: 100. |
| offset | Pagination. Default: 0. |
curl "https://depix-backend.vercel.app/api/products?active=1" \ -H "Authorization: Bearer sk_live_<your-key>"
{
"products": [
{
"id": "prd_xxx",
"name": "T-shirt M",
"slug": "tshirt-m",
"amount": 2990,
"description": "T-shirt size M",
"active": true,
"is_live": true,
"payment_url": "https://pay.depixapp.com/joao/tshirt-m"
}
],
"stats": {
"total": 5,
"active": 4
},
"limit": 50,
"offset": 0
}
Get product
Returns the details of a specific product, including checkout statistics.
curl https://depix-backend.vercel.app/api/products/prd_xxx \ -H "Authorization: Bearer sk_live_<your-key>"
{
"product": {
"id": "prd_xxx",
"name": "T-shirt M",
"slug": "tshirt-m",
"amount": 2990,
"description": "T-shirt size M",
"image_url": null,
"callback_url": null,
"redirect_url": null,
"metadata": null,
"expires_in": 1200,
"active": true,
"is_live": true,
"payment_url": "https://pay.depixapp.com/joao/tshirt-m",
"created_at": "2025-06-01T00:00:00.000Z"
}
}
Update product
Updates one or more fields of an existing product. Only send the fields you want to change.
Parameters (all optional)
| Field | Type | Description |
|---|---|---|
| name | string | New product name. 2-80 characters. |
| slug | string | New URL identifier. Same rules as creation. |
| amount | integer | New amount in centavos. |
| description | string | New description. |
| image_url | string | New image URL. |
| callback_url | string | New webhook URL. |
| redirect_url | string | New redirect URL. |
| metadata | object | New additional data. |
| expires_in | integer | New checkout expiration time. Minimum: 300 (5min). Maximum: 1200 (20min). |
curl -X PATCH https://depix-backend.vercel.app/api/products/prd_xxx \ -H "Authorization: Bearer sk_live_<your-key>" \ -H "Content-Type: application/json" \ -d '{ "amount": 3490, "description": "T-shirt size M - Special Edition" }'
{
"product": {
"id": "prd_xxx",
"name": "T-shirt M",
"slug": "tshirt-m",
"amount": 3490,
"description": "T-shirt size M - Special Edition",
"active": true,
"is_live": true,
"payment_url": "https://pay.depixapp.com/joao/tshirt-m"
}
}
Activate / Deactivate product
Activates or deactivates a product. Inactive products return a 404 error when accessed via the payment link.
curl -X POST https://depix-backend.vercel.app/api/products/prd_xxx/activate \ -H "Authorization: Bearer sk_live_<your-key>"
curl -X POST https://depix-backend.vercel.app/api/products/prd_xxx/deactivate \ -H "Authorization: Bearer sk_live_<your-key>"
{ "success": true }
Product checkouts
Lists the checkouts generated from a specific product. Accepts the same filters as the general checkout listing.
curl "https://depix-backend.vercel.app/api/products/prd_xxx/checkouts?status=completed" \ -H "Authorization: Bearer sk_live_<your-key>"
The response follows the same format as the checkout listing.
Payment links
DePix App generates permanent payment links for products and for the merchant page. These links create checkouts on demand when the customer accesses them.
Link types
| Type | URL | Behavior |
|---|---|---|
| Product | https://pay.depixapp.com/{username}/{slug} | Fixed price. The customer sees the product and clicks "Pay with PIX". |
| Merchant | https://pay.depixapp.com/{username} | Custom amount. The customer enters the amount and clicks "Pay with PIX". |
Lifecycle
- When the customer accesses the link and initiates payment, an individual checkout is created automatically.
- From there, the lifecycle is identical to a checkout created via API (status, webhooks, expiration).
- The
callback_urlfollows the chain: product field (if set) → merchant default → null. - The
redirect_urlfollows the same chain.
Public product
Returns the public data of an active product. Does not require authentication.
curl https://depix-backend.vercel.app/api/products/prd_xxx/public
{
"product": {
"id": "prd_xxx",
"name": "T-shirt M",
"slug": "tshirt-m",
"amount": 2990,
"description": "T-shirt size M",
"image_url": null
},
"merchant": {
"name": "Loja do Joao",
"username": "joao"
}
}
Product checkout
Creates a checkout from an active product. Does not require authentication. The amount is inherited from the product.
curl -X POST https://depix-backend.vercel.app/api/products/prd_xxx/checkout
The response follows the same format as create checkout (status 201).
Merchant page
Returns the merchant's public data. Does not require authentication.
curl https://depix-backend.vercel.app/api/merchants/joao/public
{
"merchant": {
"name": "Loja do Joao",
"username": "joao"
}
}
Merchant checkout
Creates a checkout with a custom amount from the merchant page. Does not require authentication.
Parameters
| Field | Type | Description | |
|---|---|---|---|
| amount | integer | required | Amount in centavos. Minimum: 500. Maximum: 300000. |
curl -X POST https://depix-backend.vercel.app/api/merchants/joao/checkout \ -H "Content-Type: application/json" \ -d '{ "amount": 5000 }'
The response follows the same format as create checkout (status 201).
Webhooks
When a checkout's status changes, the API sends a POST to the callback_url you provided when creating the checkout (or configured on the product/merchant).
How it works
- The request is sent with a 5-second timeout.
- If it fails, the API retries up to 2 more times: after 1 minute and after 10 minutes (3 attempts total).
- Your endpoint must respond with a 2xx status to confirm receipt.
- The
callback_urlmust be HTTPS and publicly accessible (no private IPs).
Events
checkout.processing
Fired when the Pix payment is received and the conversion is being processed.
{
"event": "checkout.processing",
"data": {
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "processing",
"amount": 2990,
"processing_at": "2025-06-01T15:02:00.000Z",
"metadata": { "order_id": "ORD-123" }
}
}
checkout.completed
Fired when the payment is confirmed and the DePix arrives in the merchant's wallet.
{
"event": "checkout.completed",
"data": {
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "completed",
"amount": 2990,
"completed_at": "2025-06-01T15:22:00.000Z",
"metadata": { "order_id": "ORD-123" }
}
}
checkout.cancelled
Fired when the merchant cancels the checkout via API.
{
"event": "checkout.cancelled",
"data": {
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "cancelled",
"amount": 2990,
"cancelled_at": "2025-06-01T15:05:00.000Z",
"metadata": { "order_id": "ORD-123" }
}
}
checkout.expired
Fired when the checkout expires without receiving payment.
{
"event": "checkout.expired",
"data": {
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "expired",
"amount": 2990,
"expires_at": "2025-06-01T15:30:00.000Z",
"metadata": { "order_id": "ORD-123" }
}
}
Verify signature
Each webhook comes with an X-DePix-Signature header. Always validate the signature before processing the event — this ensures the request came from the DePix App API and not from a third party.
Header format
X-DePix-Signature: t=1717257600,v1=abc123def456...
- t — Unix timestamp of the dispatch (seconds).
- v1 — HMAC-SHA256 signature in hexadecimal.
How to validate
The signature is computed over the string timestamp.payload using the Webhook Secret from your account (available in the Merchant Dashboard).
# Compute the expected signature EXPECTED=$(echo -n "${TIMESTAMP}.${RAW_BODY}" | \ openssl dgst -sha256 -hmac "${WEBHOOK_SECRET}" | awk '{print $2}') # Compare with the received v1 if [ "$EXPECTED" = "$RECEIVED_V1" ]; then echo "Valid signature" fi
import crypto from "node:crypto"; function verifyWebhook(rawBody, sigHeader, secret) { const parts = Object.fromEntries( sigHeader.split(",").map(p => p.split("=", 2)) ); const timestamp = parts["t"]; const received = parts["v1"]; const expected = crypto .createHmac("sha256", secret) .update(`${timestamp}.${rawBody}`) .digest("hex"); // Use timingSafeEqual to prevent timing attacks const a = Buffer.from(expected, "hex"); const b = Buffer.from(received, "hex"); if (a.length !== b.length) return false; return crypto.timingSafeEqual(a, b); } // Example with Express app.post("/webhook/depix", express.raw({ type: "application/json" }), (req, res) => { const sig = req.headers["x-depix-signature"]; if (!verifyWebhook(req.body.toString(), sig, process.env.DEPIX_WEBHOOK_SECRET)) { return res.status(401).send("Invalid signature"); } const { event, data } = JSON.parse(req.body); // process the event... res.sendStatus(200); });
import hmac, hashlib def verify_webhook(raw_body: str, sig_header: str, secret: str) -> bool: parts = dict(p.split("=", 1) for p in sig_header.split(",")) timestamp = parts["t"] received = parts["v1"] expected = hmac.new( secret.encode(), f"{timestamp}.{raw_body}".encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, received)
function verifyWebhook(string $rawBody, string $sigHeader, string $secret): bool { $parts = []; foreach (explode(",", $sigHeader) as $pair) { [$k, $v] = explode("=", $pair, 2); $parts[$k] = $v; } $expected = hash_hmac("sha256", $parts["t"] . "." . $rawBody, $secret); return hash_equals($expected, $parts["v1"]); }
static bool VerifyWebhook(string rawBody, string sigHeader, string secret) { var parts = sigHeader.Split(',') .ToDictionary(p => p.Split('=', 2)[0], p => p.Split('=', 2)[1]); using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); var expected = Convert.ToHexString( hmac.ComputeHash(Encoding.UTF8.GetBytes($"{parts["t"]}.{rawBody}")) ).ToLower(); return CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(parts["v1"]) ); }
func verifyWebhook(rawBody, sigHeader, secret string) bool { parts := make(map[string]string) for _, p := range strings.Split(sigHeader, ",") { kv := strings.SplitN(p, "=", 2) parts[kv[0]] = kv[1] } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(parts["t"] + "." + rawBody)) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(parts["v1"])) }
def verify_webhook(raw_body, sig_header, secret) parts = sig_header.split(",").to_h { |p| p.split("=", 2) } expected = OpenSSL::HMAC.hexdigest("sha256", secret, "#{parts['t']}.#{raw_body}") Rack::Utils.secure_compare(expected, parts["v1"]) end
static boolean verifyWebhook(String rawBody, String sigHeader, String secret) throws Exception { Map<String, String> parts = new HashMap<>(); for (String p : sigHeader.split(",")) { String[] kv = p.split("=", 2); parts.put(kv[0], kv[1]); } Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256")); String expected = HexFormat.of().formatHex( mac.doFinal((parts.get("t") + "." + rawBody).getBytes()) ); return MessageDigest.isEqual(expected.getBytes(), parts.get("v1").getBytes()); }
Sandbox
Use sk_test_... keys to test without moving real money. Checkouts created with a test key never generate a real Pix and are isolated from production checkouts.
Test mode differences
- The
is_livefield returnsfalse. - The generated QR code is not a valid Pix — it cannot be paid with a banking app.
- Use the
/simulate-paymentendpoint to mark the checkout as paid. - Webhooks are sent normally — great for testing your end-to-end integration.
Simulate payment
Marks a test checkout as paid. Only works with sk_test_ keys. Fires the checkout.completed webhook normally.
# 1. Create a test checkout curl -X POST https://depix-backend.vercel.app/api/checkouts \ -H "Authorization: Bearer sk_test_<your-key>" \ -H "Content-Type: application/json" \ -d '{ "amount": 1000, "callback_url": "https://my-store.com/webhook" }' # 2. Simulate the payment curl -X POST https://depix-backend.vercel.app/api/checkouts/chk_01jxxxxxxxxxxxxxxxxxxxxxx/simulate-payment \ -H "Authorization: Bearer sk_test_<your-key>"
{ "success": true }
After the simulation, your callback_url will receive the checkout.completed event within seconds — exactly like a real payment.
Verify key (GET /api/me)
Returns the authenticated merchant's information. Useful for verifying if the API key is valid and checking account data.
curl https://depix-backend.vercel.app/api/me \ -H "Authorization: Bearer sk_live_<your-key>"
{
"merchant_id": "mrc_xxx",
"name": "Loja do Joao",
"username": "joao",
"is_live": true,
"created_at": "2025-06-01T00:00:00.000Z"
}
Rate limits
The API enforces request limits to ensure stability and protect against abuse.
| Endpoint | Limit | Scope |
|---|---|---|
| POST /api/checkouts | 30 / min | per IP |
| GET /api/checkout-page/:id | 30 / min | per IP (public) |
| GET /api/pay/:id | 60 / min | per IP (public) |
| POST /api/pay/:id/simulate | 5 / min | per IP (public, sandbox only) |
| POST /api/merchants/:username/checkout | 10 / min | per IP (public) |
| POST /api/products/:id/checkout | 10 / min | per IP (public) |
| GET /api/products/:id/public | 30 / min | per IP (public) |
| GET /api/merchants/:username/public | 30 / min | per IP (public) |
| Per merchant (API key) | Configurable | applied after auth, on top of the per-IP limit |
- For requests authenticated with an API key, an additional rate limit applies per merchant (configurable — contact support if you need it raised).
- For public endpoints (no auth), the rate limit is applied only per IP.
- When the limit is reached, the API returns status
429with the message"Muitas requisições. Tente novamente em 1 minuto."— wait ~60s before retrying.