Início rápido
Uma integração de pagamentos mínima: a criação de uma cobrança pela API e o tratamento da notificação de pagamento. A última seção cobre a entrega do pedido. Os exemplos são autossuficientes; nenhum SDK é usado.
Trabalhando com um agente de IA? Entregue a ele a versão em markdown desta página. Ela tem detalhes suficientes para construir uma integração para o seu site ou loja.
Pré-requisitos
| Valor | Onde obter |
|---|---|
| Chave de API | A seção Integrações (Integrations) |
| ID do projeto | A seção Projetos (Projects) |
| Segredo do webhook | Configurações do projeto |
Nas mesmas configurações do projeto, defina a Webhook URL — o endereço do manipulador de notificações no seu servidor. Somente HTTPS; redirecionamentos não são seguidos.
Os exemplos usam valores de teste — substitua-os pelos seus.
Criação de uma cobrança
POST https://api.bitsby.app/invoices/create, a chave de API é passada no cabeçalho Authorization.
| Parâmetro | Obrigatório | Valor |
|---|---|---|
projectId | sim | ID do projeto, UUID |
amountFiat | sim | Valor da cobrança |
currencyFiat | sim | Moeda: USD, EUR, RUB |
timeToPay | sim | Prazo de pagamento em horas: 0.5, 1, 3, 6, 12 |
description | não | Descrição, exibida ao cliente na página de pagamento |
serviceData | não | Dados de serviço, não exibidos ao cliente. Recomendamos passar o número do pedido: ele volta na notificação de pagamento |
- cURL
- PHP
- TypeScript
- JavaScript
- Python
curl -X POST https://api.bitsby.app/invoices/create \
-H "Authorization: Token MSvL2ltaDZdWVjmZURURMVWhqSJLT2NURjhL2Fla1Z1T1IxQTltKs1T3Ay" \
-F "projectId=9deea1e2-0c08-41a3-bdc2-a34eada3892d" \
-F "amountFiat=49.90" \
-F "currencyFiat=USD" \
-F "timeToPay=1" \
-F "description=Order 4172" \
-F "serviceData=order-4172"
<?php
$apiKey = 'MSvL2ltaDZdWVjmZURURMVWhqSJLT2NURjhL2Fla1Z1T1IxQTltKs1T3Ay';
$projectId = '9deea1e2-0c08-41a3-bdc2-a34eada3892d';
$ch = curl_init('https://api.bitsby.app/invoices/create');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Authorization: Token '.$apiKey],
CURLOPT_POSTFIELDS => http_build_query([
'projectId' => $projectId,
'amountFiat' => 49.90,
'currencyFiat' => 'USD',
'timeToPay' => 1,
'description' => 'Order 4172',
'serviceData' => 'order-4172', // your order id
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['result'] !== 'success') {
exit('API error: '.$response['data']);
}
$invoice = $response['data'];
// $invoice['id'] — invoice id, store it with the order
// $invoice['url'] — payment page for the customer
const API_KEY = 'MSvL2ltaDZdWVjmZURURMVWhqSJLT2NURjhL2Fla1Z1T1IxQTltKs1T3Ay';
const PROJECT_ID = '9deea1e2-0c08-41a3-bdc2-a34eada3892d';
interface Invoice {
id: string;
uid: string;
url: string;
timeToPayDatetime: string;
}
const response = await fetch('https://api.bitsby.app/invoices/create', {
method: 'POST',
headers: { Authorization: `Token ${API_KEY}` },
body: new URLSearchParams({
projectId: PROJECT_ID,
amountFiat: '49.90',
currencyFiat: 'USD',
timeToPay: '1',
description: 'Order 4172',
serviceData: 'order-4172', // your order id
}),
signal: AbortSignal.timeout(10_000),
});
const result: { result: string; data: Invoice | string } = await response.json();
if (result.result !== 'success') {
throw new Error(`API error: ${result.data}`);
}
const invoice = result.data as Invoice;
// invoice.id — invoice id, store it with the order
// invoice.url — payment page for the customer
const API_KEY = 'MSvL2ltaDZdWVjmZURURMVWhqSJLT2NURjhL2Fla1Z1T1IxQTltKs1T3Ay';
const PROJECT_ID = '9deea1e2-0c08-41a3-bdc2-a34eada3892d';
const response = await fetch('https://api.bitsby.app/invoices/create', {
method: 'POST',
headers: { Authorization: `Token ${API_KEY}` },
body: new URLSearchParams({
projectId: PROJECT_ID,
amountFiat: '49.90',
currencyFiat: 'USD',
timeToPay: '1',
description: 'Order 4172',
serviceData: 'order-4172', // your order id
}),
signal: AbortSignal.timeout(10_000),
});
const result = await response.json();
if (result.result !== 'success') {
throw new Error(`API error: ${result.data}`);
}
const invoice = result.data;
// invoice.id — invoice id, store it with the order
// invoice.url — payment page for the customer
import requests
API_KEY = 'MSvL2ltaDZdWVjmZURURMVWhqSJLT2NURjhL2Fla1Z1T1IxQTltKs1T3Ay'
PROJECT_ID = '9deea1e2-0c08-41a3-bdc2-a34eada3892d'
response = requests.post(
'https://api.bitsby.app/invoices/create',
headers={'Authorization': f'Token {API_KEY}'},
data={
'projectId': PROJECT_ID,
'amountFiat': '49.90',
'currencyFiat': 'USD',
'timeToPay': '1',
'description': 'Order 4172',
'serviceData': 'order-4172', # your order id
},
timeout=10,
)
result = response.json()
if result['result'] != 'success':
raise RuntimeError(f"API error: {result['data']}")
invoice = result['data']
# invoice['id'] — invoice id, store it with the order
# invoice['url'] — payment page for the customer
Resposta:
{
"result": "success",
"data": {
"id": "ade9550d-3dc7-4fd3-b94e-3b4c12aaaa0c",
"uid": "MXNj4m8HhcM4",
"createDatetime": "2026-09-14 10:12:03",
"timeToPayDatetime": "2026-09-14 11:12:03",
"commissionFiatUSD": 0.5,
"amountFiatUSD": 49.9,
"url": "https://dash.bitsby.app/invoices/pay/MXNj4m8HhcM4"
}
}
Guarde data.id junto com o pedido e envie o cliente para data.url — a página de pagamento. Entregue o link como preferir: um redirecionamento, um e-mail, uma mensagem de bot. A cobrança é válida até timeToPayDatetime (UTC).
Tratamento da notificação
Quando uma cobrança passa a Paga (Paid), o serviço envia uma requisição POST para a Webhook URL do projeto. O corpo é JSON; a assinatura vem nos cabeçalhos:
| Cabeçalho | Valor |
|---|---|
X-Timestamp | Horário de envio, unix time em segundos |
X-Signature | sha256= + HMAC-SHA256 em hex da string <timestamp>.<request body> |
A chave de assinatura é o segredo do webhook. A assinatura é calculada sobre o corpo bruto da requisição, então verifique-a antes de fazer o parse do JSON.
- PHP
- TypeScript
- JavaScript
- Python
<?php
$secret = 'k7QwR2mZ9tXbN4vL8sJpH3dF6yA1cE0u';
$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
// Reject replayed requests: allow up to 5 minutes of clock drift
if (abs(time() - (int)$timestamp) > 300) {
http_response_code(400);
exit;
}
$expected = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$body, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(403);
exit;
}
$invoice = json_decode($body, true)['invoice'];
if ($invoice['status'] === 'paid') {
// $invoice['serviceData'] — the order id passed at creation: 'order-4172'
// $invoice['amountFiat'] — the original invoice amount: 49.9
// Issue the order here, see the next section
}
http_response_code(200);
import { createHmac, timingSafeEqual } from 'node:crypto';
import express, { type Request, type Response } from 'express';
const SECRET = 'k7QwR2mZ9tXbN4vL8sJpH3dF6yA1cE0u';
const app = express();
// express.raw: the signature is computed over the raw request body
app.post('/webhook', express.raw({ type: 'application/json' }), (req: Request, res: Response) => {
const timestamp = req.get('X-Timestamp') ?? '';
const signature = req.get('X-Signature') ?? '';
const body = (req.body as Buffer).toString('utf8');
// Reject replayed requests: allow up to 5 minutes of clock drift
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.sendStatus(400);
}
const expected = 'sha256=' + createHmac('sha256', SECRET)
.update(`${timestamp}.${body}`, 'utf8')
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signature);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return res.sendStatus(403);
}
const { invoice } = JSON.parse(body);
if (invoice.status === 'paid') {
// invoice.serviceData — the order id passed at creation: 'order-4172'
// invoice.amountFiat — the original invoice amount: 49.9
// Issue the order here, see the next section
}
res.sendStatus(200);
});
app.listen(8080);
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';
const SECRET = 'k7QwR2mZ9tXbN4vL8sJpH3dF6yA1cE0u';
const app = express();
// express.raw: the signature is computed over the raw request body
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Timestamp') ?? '';
const signature = req.get('X-Signature') ?? '';
const body = req.body.toString('utf8');
// Reject replayed requests: allow up to 5 minutes of clock drift
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.sendStatus(400);
}
const expected = 'sha256=' + createHmac('sha256', SECRET)
.update(`${timestamp}.${body}`, 'utf8')
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signature);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return res.sendStatus(403);
}
const { invoice } = JSON.parse(body);
if (invoice.status === 'paid') {
// invoice.serviceData — the order id passed at creation: 'order-4172'
// invoice.amountFiat — the original invoice amount: 49.9
// Issue the order here, see the next section
}
res.sendStatus(200);
});
app.listen(8080);
import hashlib
import hmac
import time
from flask import Flask, request
SECRET = 'k7QwR2mZ9tXbN4vL8sJpH3dF6yA1cE0u'
app = Flask(__name__)
@app.post('/webhook')
def webhook():
timestamp = request.headers.get('X-Timestamp', '')
signature = request.headers.get('X-Signature', '')
# The signature is computed over the raw request body
body = request.get_data(as_text=True)
# Reject replayed requests: allow up to 5 minutes of clock drift
if abs(time.time() - int(timestamp or 0)) > 300:
return '', 400
expected = 'sha256=' + hmac.new(
SECRET.encode(),
f'{timestamp}.{body}'.encode(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, signature):
return '', 403
invoice = request.get_json()['invoice']
if invoice['status'] == 'paid':
# invoice['serviceData'] — the order id passed at creation: 'order-4172'
# invoice['amountFiat'] — the original invoice amount: 49.9
# Issue the order here, see the next section
pass
return '', 200
Responda com um código 2xx em até 10 segundos. Qualquer outro código, um redirecionamento ou um timeout conta como falha de entrega: o serviço repete as tentativas em intervalos crescentes, de 5 minutos até 24 horas, e depois para. Transfira processamentos longos para uma fila: responda 200 primeiro e só então trate o pedido.
O formato completo da notificação e a referência dos campos estão na seção Webhook URL.
Entrega do pedido
Uma notificação verificada com status paid confirma o pagamento. Etapas da entrega:
- Encontre o pedido por
invoice.serviceData— o valor passado na criação da cobrança (order-4172). - Verifique por
invoice.idse esta cobrança já foi entregue. Uma notificação com o mesmoinvoice.idpode chegar mais de uma vez — entregue o pedido uma única vez e guarde uma marca de processado. - Confira o valor e a moeda com
invoice.amountFiateinvoice.currencyFiat— esses são os valores originais da cobrança e nunca mudam.amountFiatUSDé sobrescrito com o valor efetivamente recebido. Compare valores como números, não como strings: zeros à direita são descartados, então49.90chega como49.9. Se os valores não corresponderem ao pedido, encaminhe a cobrança para análise manual em vez da entrega — esses casos são cobertos em Vinculação de pagamentos e divergências de valores. - Entregue o pedido e marque-o como processado.
Não verifique o prazo de pagamento na entrega: o pagamento pode ter sido confirmado on-chain depois de timeToPayDatetime, e o lojista pode vincular um pagamento a uma cobrança manualmente. A própria notificação confirma o pagamento.
O que vem a seguir
- Referência da API — listas de cobranças, cancelamento, estatísticas, saldos
- Verificação da assinatura do webhook — a mecânica da assinatura em detalhes
- Vinculação de pagamentos e divergências de valores — pagamento a menor, a maior e pagamentos não vinculados
- Formulários HTML — aceitação de pagamentos sem a API