MobiariDocs

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étodoCaminhoFinalidade
GET/v1/stores/{store_id}/webhooksLista os webhooks.
POST/v1/stores/{store_id}/webhooksCria. 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}/deliveriesTentativas de entrega, com status e corpo da resposta.
GET/v1/webhook-events?webhook_id=…Eventos em que o webhook está inscrito.
POST/v1/webhook-eventsInscreve: {"webhook_id", "events": [...]}.
DELETE/v1/webhook-eventsCancela a inscrição: {"webhook_id", "event"}.

Eventos comuns

EventoDisparado quando
orders.createdUm novo pedido é registrado (intenção de pagamento emitida).
orders.paidO provedor de pagamento confirmou o pagamento.
orders.fulfilled, orders.shipped, orders.deliveredAndamento do envio.
orders.cancelledO pedido foi cancelado.
orders.refundedUm reembolso total foi concluído no provedor.
orders.updatedQualquer outra alteração em um pedido.
products.created, products.updated, products.deletedAlterações no catálogo, incluindo mídia.
inventory.low_stock, inventory.back_in_stockO estoque cruzou um limite.
customers.created, customers.updated, customers.deletedCadastros de clientes.
carts.abandoned, carts.recoveredRecuperaçã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:

HeaderValor
X-Webhook-Signaturesha256=<base64 HMAC>, veja abaixo
X-Webhook-EventO nome do evento, igual ao event no corpo
X-Webhook-Event-IdUUID do evento. É o mesmo em todas as novas tentativas: use-o para eliminar duplicatas.
User-AgentMobiari-Webhook/1.0
Content-Typeapplication/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 processed

Testes 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çãoPermissão
Listar, consultarwebhooks.read
Criarwebhooks.write
Editarwebhooks.edit
Excluirwebhooks.delete

Nesta página