Formulários HTML
Uma das formas de adicionar pagamentos em cripto a um site, uma loja virtual ou um bot. O cliente clica em um botão, e a página de pagamento com a cobrança se abre.
O formulário é enviado somente via POST para https://dash.bitsby.app/invoices/form e contém dois campos:
| Campo | O que contém |
|---|---|
data | base64url do JSON com os campos da cobrança |
signature | HMAC-SHA256 da string data, 64 caracteres hex |
Um projeto pode ter no máximo 50 cobranças em aberto criadas dessa forma a cada momento.
Exemplo de formulário
<form method="post" action="https://dash.bitsby.app/invoices/form" target="_blank">
<input type="hidden" name="data" value="eyJwcm9qZWN0SWQiOiJhMWIyYzNkNC01ZTZm...">
<input type="hidden" name="signature" value="f77d6da1be35bd2900e0bfed9f202b04...">
<button type="submit">Pay</button>
</form>
O servidor da loja preenche os dois valores ao renderizar a página. O segredo nunca aparece na marcação.
O atributo target="_blank" é opcional: com ele, o carrinho permanece aberto na aba original.
Como o contêiner é montado
- Monte um objeto JSON com os campos da cobrança.
- Codifique-o como base64url. Essa é a string
data. - Calcule
signature = HMAC-SHA256(data, formSecret).
A string data é assinada como um todo, então a ordem das chaves no JSON, a indentação e o estilo de escape Unicode não importam. O receptor também aceita base64 padrão, com ou sem padding.
Não remonte data depois de assinar: se um único byte mudar, a assinatura precisa ser calculada de novo.
Como calcular a assinatura, com exemplos prontos em quatro linguagens, está em Assinatura do formulário HTML.
Chaves dentro de data
| Chave | Obrigatória | O que contém |
|---|---|---|
projectId | sim | ID do projeto das configurações no painel, uuid |
amountFiat | sim | Valor de 1 a 100.000, no máximo duas casas decimais. Como string "10.50" ou número 10.5 |
currencyFiat | sim | Moeda fiat: USD, EUR ou RUB |
timeToPay | sim | Prazo de pagamento em horas: 0.5, 1, 3, 6, 12. Como string ou número |
description | não | Descrição para o cliente, até 1.000 caracteres |
serviceData | não | Identificador do pedido no lado da loja, até 1.000 caracteres. Não é exibido ao cliente, mas fica visível no código-fonte do formulário — não coloque nada sensível aqui |
output | não | errors — exibir os detalhes dos erros de validação |
Uma chave opcional pode ser omitida do JSON — é o mesmo que uma string vazia. As chaves podem vir em qualquer ordem; o receptor ignora chaves desconhecidas.
Os valores são strings ou números JSON. Booleanos, arrays e objetos aninhados não contam como valores de campo e chegam como uma string vazia.
Exemplo do conteúdo de data:
{
"projectId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"amountFiat": 10.5,
"currencyFiat": "USD",
"timeToPay": 1,
"description": "Order #7, delivery",
"serviceData": "order-7"
}
Aqui o valor e o prazo de pagamento são passados como números; strings também são aceitas. O output opcional não foi definido.
Formato do valor
Somente dígitos e ponto decimal, no máximo duas casas decimais: 10, 10.00, 1000.50. Espaços ao redor são removidos.
Qualquer outro formato é rejeitado — separadores de milhar, vírgula em vez de ponto, notação exponencial, sinal antes do número, símbolo de moeda. Não há arredondamento: uma terceira casa decimal não é truncada, ela causa rejeição.
Segredo do formulário
O segredo fica nas configurações do projeto no painel, no campo Segredo do formulário (Form secret). O servidor o emite quando o projeto é criado. Não é possível definir um valor próprio; o segredo só pode ser reemitido — por exemplo, se for comprometido.
O segredo deve permanecer no servidor da loja. Se ele acabar no HTML da vitrine, a assinatura perde o sentido.
O segredo do formulário e o segredo do webhook são chaves diferentes; não os confunda.
Identificador do pedido
Sempre preencha serviceData: coloque ali o seu próprio identificador do pedido. O valor volta na notificação de pagamento — a loja o usa para encontrar o pedido.
Um serviceData preenchido protege contra duplicatas. Reenviar o formulário ou clicar duas vezes no botão não cria uma segunda cobrança: o cliente é enviado à cobrança em aberto existente. A correspondência é feita pelos campos do pedido — projeto, valor, moeda, descrição e serviceData — e não pelos bytes de data.
O valor deve ser único por pedido. Com o mesmo valor, um segundo cliente cai na cobrança em aberto do primeiro.
Um serviceData vazio remove essa proteção: não há pelo que reconhecer uma repetição, e cada envio cria uma nova cobrança. As duplicatas consomem o limite de 50 cobranças em aberto e, quando uma é paga, a loja não tem como ligar o pagamento a um pedido.
Erros e modo de depuração
O formulário passa por duas verificações: primeiro a assinatura e o contêiner, depois os valores dos campos.
| O que aconteceu | O que o serviço exibe |
|---|---|
| A assinatura não corresponde, o contêiner é ilegível, o projeto pertence a outra pessoa ou não existe | Um erro genérico, sem indicar o motivo |
| A assinatura corresponde, mas um campo está preenchido incorretamente | Um erro genérico. Com "output":"errors" — erros detalhados |
| As duas verificações passaram | A página de pagamento com a cobrança |
A primeira verificação nunca revela o motivo, sob nenhuma configuração.
O par "output":"errors" no JSON ativa os detalhes da segunda verificação: o serviço nomeia o campo, lista os valores permitidos e informa quando o teto de 50 cobranças em aberto é atingido. A chave fica dentro do contêiner assinado, então não pode ser injetada de fora.
Durante a configuração do formulário, coloque "output":"errors" no JSON; depois de configurado, remova a chave e remonte o contêiner.
Se a própria assinatura não corresponder, compare seus data e signature com o conjunto de referência em Assinatura do formulário HTML.
O que vem a seguir
Uma cobrança criada pelo formulário é processada da mesma forma que uma criada pela API ou no painel:
- A confirmação do pagamento chega à Webhook URL. Verifique a assinatura da notificação com o segredo do webhook e entregue os pedidos somente com base nela.
- O retorno do cliente ao seu site é configurado pela URL de sucesso (Successful URL) e pela URL de falha (Unsuccessful URL) — veja a seção “Retorno do cliente ao site da loja” na página Ciclo de vida da cobrança. Chegar a essas URLs não confirma o pagamento.
- Os status da cobrança e a janela de busca de pagamentos estão descritos em Ciclo de vida da cobrança.
- O cliente transferiu um valor errado — veja Vinculação de pagamentos e divergências de valores.