Desenvolvedores
Estoque por API
Venda os itens que ficam no seu próprio sistema, sem copiar nada para a loja.
Com o estoque por API, as contas, chaves e licenças continuam no seu sistema. A loja pergunta quanto você tem, reserva os itens na hora em que o pedido nasce e devolve se o Pix vencer. Quem compra recebe exatamente o que a sua API entregou, e o mesmo item nunca sai duas vezes, mesmo que você venda também no seu site, num bot ou em outra loja.
Se preferir mandar as linhas para a loja guardar, use a rota de Estoque da API pública.
Como ligar#
Cadastre a API#
Em Integrações > Estoque por API, clique em Cadastrar API, dê um nome e informe a URL do seu sistema. Em produção ela precisa ser
https, na porta 443, e apontar para um endereço público.Guarde o segredo#
O segredo aparece uma vez só. O seu sistema usa ele para conferir que cada chamada veio da Promisse. Se perder, gere outro em Novo segredo: o antigo para de valer na hora.
Quando sai da equipe quem viu o segredo atual ou apontou a URL, a API ganha o selo Trocar segredo e o dono recebe um aviso. A troca não é automática, para as vendas não pararem sem você saber: confira se a URL ainda é do seu sistema, gere um novo segredo e atualize o seu sistema.
Teste a conexão#
Testar manda a ação
pinge mostra o código e o tempo da resposta.Ligue o produto#
No editor do produto, no cartão Estoque, clique em Ligar a uma API, escolha a API e informe o ID do produto no seu sistema. A loja chama
stockcom esse ID na hora e só liga se a sua API reconhecer o produto.
Depois de ligado, o produto passa a ter entrega automática e mostra a quantidade da sua API, atualizada a cada minuto e a cada venda. Ele não aceita variações nem linhas de estoque da loja, e vende até 100 unidades por pedido. Produto entregue no jogo e produto que já usa o estoque de um bot não podem ser ligados.
A requisição#
Toda chamada é um POST em JSON na URL cadastrada, com a ação no campo action.
| Cabeçalho | Conteúdo |
|---|---|
content-type |
application/json |
user-agent |
PromisseStore-Stock/1.0 |
x-promisse-action |
A mesma ação do corpo |
x-promisse-timestamp |
Hora do envio, em segundos desde 1970 |
x-promisse-signature |
HMAC-SHA256 em hexadecimal de timestamp.corpo, com o segredo como chave |
A loja espera a resposta por até 8 segundos, não segue redirecionamento e lê no máximo 64 KB (2 MB na reserve). A resposta precisa ser JSON.
Validar a assinatura#
Junte o timestamp, um ponto e os bytes exatos do corpo, antes de converter o JSON. Recuse chamadas com mais de 5 minutos: assim uma chamada copiada não vale de novo.
import crypto from 'node:crypto';
import http from 'node:http';
const SECRET = process.env.PROMISSE_STOCK_SECRET;
if (!SECRET) throw new Error('Defina PROMISSE_STOCK_SECRET');
// Troque por um banco. Cada item tem o texto entregue e a referência de quem reservou.
const stock = {
licenca: [
{ text: 'AAAA-BBBB-CCCC', reference: null },
{ text: 'DDDD-EEEE-FFFF', reference: null },
],
};
function isValid(rawBody, headers) {
const timestamp = String(headers['x-promisse-timestamp'] ?? '');
if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = crypto.createHmac('sha256', SECRET).update(`${timestamp}.`).update(rawBody).digest('hex');
const received = String(headers['x-promisse-signature'] ?? '');
return expected.length === received.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}
function readBody(req) {
return new Promise((resolve, reject) => {
const chunks = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', () => resolve(Buffer.concat(chunks)));
req.on('error', reject);
});
}
const free = (items) => items.filter((item) => !item.reference);
function handle(call) {
const items = stock[call.product];
switch (call.action) {
case 'ping':
return [200, { ok: true }];
case 'stock':
return [200, { products: call.products.filter((id) => stock[id]).map((id) => ({ id, stock: free(stock[id]).length })) }];
case 'reserve': {
if (!items) return [404, { error: 'Produto não existe' }];
// A mesma referência devolve os mesmos itens: a loja pode repetir a chamada.
const mine = items.filter((item) => item.reference === call.reference);
const take = free(items).slice(0, Math.max(0, call.quantity - mine.length));
if (mine.length + take.length < call.quantity) return [409, { error: 'Sem estoque' }];
for (const item of take) item.reference = call.reference;
return [200, { items: [...mine, ...take].slice(0, call.quantity).map((item) => item.text) }];
}
case 'release': {
const mine = (items ?? []).filter((item) => item.reference === call.reference);
for (const item of mine) item.reference = null;
return [200, { released: mine.length }];
}
case 'confirm':
// Reservado e pago: marque como vendido e guarde o pedido e o e-mail, se precisar.
return [200, { ok: true }];
default:
return [200, { ok: true }];
}
}
http.createServer(async (req, res) => {
if (req.method !== 'POST' || req.url !== '/promisse/estoque') return res.writeHead(404).end();
const rawBody = await readBody(req);
if (!isValid(rawBody, req.headers)) return res.writeHead(401, { 'content-type': 'application/json' }).end('{"error":"Assinatura inválida"}');
const [status, body] = handle(JSON.parse(rawBody));
res.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify(body));
}).listen(3000);Para testar, rode com PROMISSE_STOCK_SECRET=seu_segredo node estoque.js num endereço público e cadastre https://seu-dominio/promisse/estoque.
Ações#
ping#
Enviada pelo botão Testar. Qualquer resposta 2xx conta como funcionando.
{ "action": "ping" }stock#
A quantidade de cada produto. A loja chama a cada minuto com todos os IDs ligados, em lotes de até 200, e também na hora de ligar um produto.
{ "action": "stock", "products": ["licenca", "curso"] }{ "products": [{ "id": "licenca", "stock": 12 }, { "id": "curso", "unlimited": true }] }| Campo | Regra |
|---|---|
stock |
Quantos itens livres há. Negativo, vazio ou texto conta como zero |
unlimited |
true vende sem limite. A reserve continua sendo chamada a cada venda |
ID que não vier na resposta fica esgotado no site.
reserve#
Chamada quando o pedido nasce, antes do pagamento. Separe os itens para aquela referência e devolva o conteúdo que o comprador vai receber.
{
"action": "reserve",
"product": "licenca",
"quantity": 2,
"reference": "4a8f0c1e-5b7d-4e8a-9c2f-1d3e5f7a9b0c",
"order": { "id": "e2b1c4d6-8f0a-4b2c-9d4e-6f8a0b2c4d6e", "number": 100231 }
}{ "items": ["AAAA-BBBB-CCCC", "DDDD-EEEE-FFFF"] }| Regra | Por quê |
|---|---|
| Tudo ou nada | Sem itens suficientes, responda 409 e não separe nada |
Mesma reference, mesmos itens |
A loja pode repetir a chamada, por exemplo num pagamento que chega depois do Pix vencer |
Exatamente quantity itens em items |
Cada item é um texto de até 10.000 caracteres |
| Item reservado não volta à venda | Até chegar a release com a mesma referência |
Num banco de dados, faça a reserva numa transação. Em Postgres:
BEGIN;
UPDATE itens SET referencia = $1
WHERE id IN (
SELECT id FROM itens
WHERE produto = $2 AND referencia IS NULL
ORDER BY id LIMIT $3
FOR UPDATE SKIP LOCKED)
RETURNING texto;
-- Se voltaram menos linhas que $3: ROLLBACK e responda 409. Senão: COMMIT.release#
O pedido venceu ou foi cancelado: solte os itens daquela referência. Pode chegar mais de uma vez, e também para uma reserva que a sua API concluiu depois de a loja desistir de esperar.
{ "action": "release", "product": "licenca", "reference": "4a8f0c1e-5b7d-4e8a-9c2f-1d3e5f7a9b0c" }{ "released": 2 }404 também conta como devolvido.
confirm#
O pedido foi pago e entregue ao comprador. Use para marcar os itens como vendidos de vez ou para registrar o comprador. Se não precisar, responda 200 e ignore.
{
"action": "confirm",
"product": "licenca",
"reference": "4a8f0c1e-5b7d-4e8a-9c2f-1d3e5f7a9b0c",
"quantity": 2,
"order": { "id": "e2b1c4d6-8f0a-4b2c-9d4e-6f8a0b2c4d6e", "number": 100231, "email": "cliente@email.com", "paid_at": "2026-11-03T18:10:00.000Z" }
}Quando a sua API falha#
| Ação | O que acontece |
|---|---|
stock |
O site mantém a última quantidade e tenta de novo no minuto seguinte |
reserve |
O comprador vê "estoque indisponível, tente de novo". O pedido é cancelado e a loja manda release com a mesma referência |
release |
A loja tenta de novo mais tarde |
confirm |
A loja tenta até 5 vezes. A entrega ao comprador não depende disso |
Em Integrações > Estoque por API, cada API mostra se está funcionando e o último erro, com o nome da ação em que ele aconteceu.

