Webhooks
Receba um POST sempre que algo relevante acontecer na sua loja.
Um webhook é uma URL que você cadastra e para a qual o Mobiari envia um POST quando um evento acontece. Use webhooks para manter sistemas externos (ERPs, agentes de IA, contabilidade, Slack) sincronizados sem precisar fazer polling.
Configuração
Crie o webhook no painel, em Webhooks, ou pela API. Primeiro o endpoint:
curl -X POST "https://api.mobiari.com/v1/stores/$STORE_ID/webhooks" \
-H "Authorization: Bearer $BP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.example.com/mobiari/webhook",
"secret": "a-long-random-string-that-stays-on-your-server"
}'A resposta é o webhook, incluindo o id e o secret. Se você omitir o
secret, um é gerado automaticamente. Ele é usado para
verificar assinaturas.
Em seguida, inscreva o webhook nos eventos:
curl -X POST "https://api.mobiari.com/v1/webhook-events" \
-H "Authorization: Bearer $BP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"webhook_id": "'"$WEBHOOK_ID"'", "events": ["orders.created", "orders.paid"]}'| Método | Caminho | Finalidade |
|---|---|---|
GET | /v1/stores/{store_id}/webhooks | Lista os webhooks. |
POST | /v1/stores/{store_id}/webhooks | Cria. Corpo: url, secret opcional, is_active. |
PUT | /v1/stores/{store_id}/webhooks/{webhook_id} | Altera url, secret ou is_active. |
DELETE | /v1/stores/{store_id}/webhooks/{webhook_id} | Exclui. |
GET | /v1/stores/{store_id}/webhooks/{webhook_id}/deliveries | Tentativas de entrega, com status e corpo da resposta. |
GET | /v1/webhook-events?webhook_id=… | Eventos em que o webhook está inscrito. |
POST | /v1/webhook-events | Inscreve: {"webhook_id", "events": [...]}. |
DELETE | /v1/webhook-events | Cancela a inscrição: {"webhook_id", "event"}. |
Eventos comuns
| Evento | Disparado quando |
|---|---|
orders.created | Um novo pedido é registrado (intenção de pagamento emitida). |
orders.paid | O provedor de pagamento confirmou o pagamento. |
orders.fulfilled, orders.shipped, orders.delivered | Andamento do envio. |
orders.cancelled | O pedido foi cancelado. |
orders.refunded | Um reembolso total foi concluído no provedor. |
orders.updated | Qualquer outra alteração em um pedido. |
products.created, products.updated, products.deleted | Alterações no catálogo, incluindo mídia. |
inventory.low_stock, inventory.back_in_stock | O estoque cruzou um limite. |
customers.created, customers.updated, customers.deleted | Cadastros de clientes. |
carts.abandoned, carts.recovered | Recuperação de carrinho. |
A lista completa é o catálogo de eventos no backend
(backend/crates/bus/src/events/catalog.rs). Novos eventos são
adicionados com o tempo; o seu handler deve confirmar o recebimento dos
eventos que ele não trata.
Formato da entrega
Toda entrega é um POST com o mesmo envelope:
{
"event": "orders.paid",
"store_id": "0199a4f2-6c1e-7d3a-9b1f-3e2c8a5d7f10",
"timestamp": 1790860251,
"data": {
"id": "…",
"status": "paid",
"...": "event-specific payload"
}
}timestamp é o momento em que o evento aconteceu, em segundos Unix.
Valores monetários dentro de data estão em centavos inteiros, como em
todo o resto da API.
Headers de toda entrega:
| Header | Valor |
|---|---|
X-Webhook-Signature | sha256=<base64 HMAC>, veja abaixo |
X-Webhook-Event | O nome do evento, igual ao event no corpo |
X-Webhook-Event-Id | UUID do evento. É o mesmo em todas as novas tentativas: use-o para eliminar duplicatas. |
User-Agent | Mobiari-Webhook/1.0 |
Content-Type | application/json |
Responda com qualquer 2xx em até 30 segundos. Qualquer outra resposta é considerada falha e a entrega é tentada novamente.
Verificação de assinaturas
Toda entrega traz um header X-Webhook-Signature:
X-Webhook-Signature: sha256=<base64>A assinatura é HMAC-SHA256(secret, raw_request_body) codificada em
base64 (alfabeto padrão, com padding =). Calcule o HMAC sobre os bytes
brutos do corpo, não sobre o JSON já interpretado: serializar de novo
alteraria os espaços em branco e quebraria a comparação.
import crypto from 'node:crypto'
export function verify(req, secret) {
const header = req.headers['x-webhook-signature'] ?? ''
const sig = header.replace(/^sha256=/, '')
const expected = crypto
.createHmac('sha256', secret)
.update(req.rawBody) // raw bytes — see your framework's docs
.digest('base64')
// Constant-time compare; both buffers must be the same length first.
const a = Buffer.from(sig)
const b = Buffer.from(expected)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}Compare sempre com uma função de tempo constante. Um === simples vaza
informações sobre o secret para quem fizer um ataque de temporização.
Novas tentativas
Entregas que falham (resposta fora de 2xx, timeout, erro de conexão) são tentadas novamente a partir de uma fila durável, com backoff exponencial: aproximadamente 10 segundos, depois 20, 40 e 80, em até 5 tentativas. As novas tentativas sobrevivem aos nossos deploys. Depois da última tentativa, a entrega é marcada como falha e não é mais repetida automaticamente.
Cada tentativa é registrada com o status e o corpo da resposta. Consulte
no painel em Webhooks → Entregas, ou com
GET /v1/stores/{store_id}/webhooks/{webhook_id}/deliveries.
Mantenha seus handlers rápidos
O ciclo completo de novas tentativas dura cerca de dois minutos e meio, então um endpoint que fica fora do ar por mais tempo perde entregas. Confirme a entrega (retorne 2xx) assim que tiver validado a assinatura e salvo o payload, e faça o processamento de forma assíncrona.
Idempotência
O mesmo evento pode chegar mais de uma vez, por exemplo em uma nova
tentativa depois que o seu handler estourou o tempo limite. Elimine
duplicatas usando o X-Webhook-Event-Id, que é o mesmo em todas as
tentativas de um evento. O corpo também é idêntico entre as tentativas.
key = f"mobiari:webhook:{request.headers['X-Webhook-Event-Id']}"
if not redis.set(key, 1, nx=True, ex=86400):
return ack() # already processedTestes locais
Abra um túnel (cloudflared, ngrok) apontando para o seu servidor de
desenvolvimento, cadastre a URL pública como webhook em uma loja de
desenvolvimento e dispare um evento pelo painel. A aba
Webhooks → Entregas mostra tudo o que foi tentado, incluindo as falhas,
com os corpos das respostas. Isso ajuda quando o seu handler está
retornando o status errado.
Resumo de permissões
| Ação | Permissão |
|---|---|
| Listar, consultar | webhooks.read |
| Criar | webhooks.write |
| Editar | webhooks.edit |
| Excluir | webhooks.delete |
Pedidos
Ciclo de vida, status e o fluxo de reembolso que de fato aciona o provedor de pagamento.
Caixa de entrada em tempo real
Os dois WebSockets por trás da caixa de entrada compartilhada e do chat da loja virtual. URLs, autenticação, todos os eventos, heartbeats, reconexões e como recuperar o que ficou para trás via REST.