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.

To get started, create an account, activate your merchant account in the Merchant Dashboard, and generate an API key.

Authentication

Use an API key in the Authorization header on all authenticated requests.

Header
Authorization: Bearer sk_live_<your-key>

Key types

PrefixTypeBehavior
sk_live_LiveReal checkouts. Real money.
sk_test_TestTest 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.

Keep your key safe. It is only displayed once at creation time. If you lose it, revoke it and generate a new one.

Errors

Errors follow the format below. The errorMessage field is always human-readable.

Error response
{
  "response": {
    "errorMessage": "Invalid data.",
    "errors": [                         // present on validation errors
      { "field": "amount", "message": "Required." }
    ]
  }
}
StatusMeaning
400Invalid data — check the fields.
401API key missing, invalid, or revoked.
403Permission denied. Merchant account not set up, or key lacks access to the resource.
404Resource not found.
409State conflict — operation not allowed in the current status.
429Rate limit exceeded. Wait and try again.
500Internal server error. Try again in a few moments.
503Service 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.

POST /api/checkouts

Parameters

FieldTypeDescription
amountintegerrequiredAmount in centavos. Minimum: 500 (R$ 5.00). Maximum: 300000 (R$ 3,000.00).
descriptionstringoptionalOrder description. Maximum 500 characters. Displayed on the payment page.
expires_inintegeroptionalExpiration time in seconds. Default: 1200 (20min). Minimum: 300 (5min). Maximum: 1200 (20min).
image_urlstringoptionalHTTPS URL of the product image. Displayed on the payment page.
callback_urlstringoptionalHTTPS URL that receives the checkout webhooks.
redirect_urlstringoptionalURL to redirect the customer after payment.
metadataobjectoptionalAdditional 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());
Response — 201 Created
{
  "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
  }
}
💡 Display the 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.

GET /api/checkouts/:id
curl
curl https://depix-backend.vercel.app/api/checkouts/chk_01jxxxxxxxxxxxxxxxxxxxxxx \
  -H "Authorization: Bearer sk_live_<your-key>"
Response — 200 OK
{
  "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

StatusMeaning
pendingAwaiting payment.
processingPix received, processing conversion to DePix.
completedPayment confirmed. DePix in the merchant's wallet.
cancelledCancelled by the merchant.
expiredPayment deadline expired.

List checkouts

Lists the merchant's checkouts with filters and pagination.

GET /api/checkouts

Query params (all optional)

ParameterDescription
statusFilter by status: pending, completed, cancelled, expired.
product_idFilter by product. E.g.: prd_xxx.
fromStart date (ISO 8601). E.g.: 2025-06-01T00:00:00Z.
toEnd date (ISO 8601).
qSearch by ID or description.
limitNumber of results per page. Default: 50. Maximum: 100.
offsetPagination. Default: 0.
curl
curl "https://depix-backend.vercel.app/api/checkouts?status=completed&limit=20" \
  -H "Authorization: Bearer sk_live_<your-key>"
Response — 200 OK
{
  "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.

POST /api/checkouts/:id/cancel
curl
curl -X POST https://depix-backend.vercel.app/api/checkouts/chk_01jxxxxxxxxxxxxxxxxxxxxxx/cancel \
  -H "Authorization: Bearer sk_live_<your-key>"
Response — 200 OK
{ "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.

POST /api/products

Parameters

FieldTypeDescription
namestringrequiredProduct name shown in the UI. 2-80 characters.
slugstringoptionalURL identifier. If omitted, auto-generated from name. Lowercase letters, numbers, and hyphens. 2-60 characters. Cannot start/end with a hyphen.
amountintegerrequiredAmount in centavos. Minimum: 500. Maximum: 300000.
descriptionstringoptionalProduct description. Maximum 500 characters.
image_urlstringoptionalHTTPS URL of the product image.
callback_urlstringoptionalHTTPS URL for webhooks. Overrides the merchant default.
redirect_urlstringoptionalRedirect URL. Overrides the merchant default.
metadataobjectoptionalAdditional data. Maximum 4KB. Included in webhooks for generated checkouts.
expires_inintegeroptionalCheckout 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());
Response — 201 Created
{
  "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.

GET /api/products

Query params (all optional)

ParameterDescription
activeFilter by status: 1 (active) or 0 (inactive).
qSearch by name, slug, or description.
limitNumber of results. Default: 50. Maximum: 100.
offsetPagination. Default: 0.
curl
curl "https://depix-backend.vercel.app/api/products?active=1" \
  -H "Authorization: Bearer sk_live_<your-key>"
Response — 200 OK
{
  "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.

GET /api/products/:id
curl
curl https://depix-backend.vercel.app/api/products/prd_xxx \
  -H "Authorization: Bearer sk_live_<your-key>"
Response — 200 OK
{
  "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.

PATCH /api/products/:id

Parameters (all optional)

FieldTypeDescription
namestringNew product name. 2-80 characters.
slugstringNew URL identifier. Same rules as creation.
amountintegerNew amount in centavos.
descriptionstringNew description.
image_urlstringNew image URL.
callback_urlstringNew webhook URL.
redirect_urlstringNew redirect URL.
metadataobjectNew additional data.
expires_inintegerNew checkout expiration time. Minimum: 300 (5min). Maximum: 1200 (20min).
curl
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" }'
Response — 200 OK
{
  "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.

POST /api/products/:id/activate
POST /api/products/:id/deactivate
curl — activate
curl -X POST https://depix-backend.vercel.app/api/products/prd_xxx/activate \
  -H "Authorization: Bearer sk_live_<your-key>"
curl — deactivate
curl -X POST https://depix-backend.vercel.app/api/products/prd_xxx/deactivate \
  -H "Authorization: Bearer sk_live_<your-key>"
Response — 200 OK
{ "success": true }

Product checkouts

Lists the checkouts generated from a specific product. Accepts the same filters as the general checkout listing.

GET /api/products/:id/checkouts
curl
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.

Public product

Returns the public data of an active product. Does not require authentication.

GET /api/products/:id/public
curl
curl https://depix-backend.vercel.app/api/products/prd_xxx/public
Response — 200 OK
{
  "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.

POST /api/products/:id/checkout
curl
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.

GET /api/merchants/:username/public
curl
curl https://depix-backend.vercel.app/api/merchants/joao/public
Response — 200 OK
{
  "merchant": {
    "name":     "Loja do Joao",
    "username": "joao"
  }
}

Merchant checkout

Creates a checkout with a custom amount from the merchant page. Does not require authentication.

POST /api/merchants/:username/checkout

Parameters

FieldTypeDescription
amountintegerrequiredAmount in centavos. Minimum: 500. Maximum: 300000.
curl
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_url must 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).

Bash
# 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
Node.js
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);
});
Python
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)
PHP
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"]);
}
C#
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"])
    );
}
Go
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"]))
}
Ruby
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
Java
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());
}
Read the body as raw bytes (before JSON parsing). Any reformatting will invalidate the signature.

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_live field returns false.
  • The generated QR code is not a valid Pix — it cannot be paid with a banking app.
  • Use the /simulate-payment endpoint to mark the checkout as paid.
  • Webhooks are sent normally — great for testing your end-to-end integration.
The sandbox is independent from production. You can create, simulate, and cancel test checkouts without risk.

Simulate payment

Marks a test checkout as paid. Only works with sk_test_ keys. Fires the checkout.completed webhook normally.

POST /api/checkouts/:id/simulate-payment
curl
# 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>"
Response — 200 OK
{ "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.

GET /api/me
curl
curl https://depix-backend.vercel.app/api/me \
  -H "Authorization: Bearer sk_live_<your-key>"
Response — 200 OK
{
  "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.

EndpointLimitScope
POST /api/checkouts30 / minper IP
GET /api/checkout-page/:id30 / minper IP (public)
GET /api/pay/:id60 / minper IP (public)
POST /api/pay/:id/simulate5 / minper IP (public, sandbox only)
POST /api/merchants/:username/checkout10 / minper IP (public)
POST /api/products/:id/checkout10 / minper IP (public)
GET /api/products/:id/public30 / minper IP (public)
GET /api/merchants/:username/public30 / minper IP (public)
Per merchant (API key)Configurableapplied 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 429 with the message "Muitas requisições. Tente novamente em 1 minuto." — wait ~60s before retrying.
If you need higher limits for your integration, contact support.