Desenvolvedores
Webhook de pedidos
A cada pedido criado, pago ou com status alterado, o LojaBio faz um POST com o pedido em JSON para o endereço que o lojista cadastrou em Painel → Integrações → Integração com ERP. É assim que o pedido chega ao Bling, Tiny, Omie ou a um sistema próprio, direto ou por um conector como Make, Zapier ou Pluga.
Como funciona
O envio é só de ida: o LojaBio manda o pedido e não lê nada de volta. Estoque e preço do ERP não voltam para a vitrine. O recurso faz parte dos planos Profissional e Negócio.
- Método
POST, corpo em JSON (Content-Type: application/json), codificação UTF-8. - O endereço precisa ser público e começar com
https://. Não são aceitos IP direto,localhost, nomes sem domínio nem endereços que resolvam para rede interna. Redirecionamentos não são seguidos. - Cada envio leva uma assinatura HMAC-SHA256 feita com o segredo que aparece uma única vez, quando o lojista salva o endereço. Gerar novo segredo no painel invalida o anterior na hora.
- O botão Enviar teste manda um pedido de exemplo completo, bom para mapear os campos no conector.
Eventos
O nome do evento vem no cabeçalho X-LojaBio-Event e no campo event do corpo.
| Evento | Quando é enviado |
|---|---|
pedido.criado | O cliente fechou o pedido na vitrine. |
pedido.pago | O pagamento foi confirmado: pelo serviço de pagamento conectado (Asaas, Mercado Pago, InfinitePay) ou pelo lojista, ao marcar um Pix como recebido. |
pedido.status | O lojista mudou o status do pedido no painel (visto, concluído, cancelado), ou o serviço de pagamento avisou que o pagamento foi estornado, contestado ou cancelado. Pode chegar várias vezes para o mesmo pedido. |
teste | Disparado pelo botão Enviar teste. O corpo traz test: true, um campo message e um pedido de exemplo com identificadores zerados. |
Todo evento carrega o pedido inteiro no estado daquele momento, não só o que mudou. Estorno, contestação e cancelamento avisados pelo serviço de pagamento chegam num pedido.status, com o novo valor em payment.status. O vencimento por prazo (expirado depois de 72 horas sem pagamento) não gera envio próprio: aparece no evento seguinte do mesmo pedido.
Cabeçalhos
| Cabeçalho | Conteúdo |
|---|---|
X-LojaBio-Event | Nome do evento, igual ao campo event. |
X-LojaBio-Delivery | Identificador da entrega (UUID). É o mesmo em todas as tentativas do mesmo envio. |
X-LojaBio-Timestamp | Momento da tentativa, em segundos desde 1970 (Unix). Muda a cada tentativa. |
X-LojaBio-Signature | sha256= seguido do HMAC-SHA256 em hexadecimal minúsculo. Veja como conferir abaixo. |
User-Agent | LojaBio-Webhook/1 |
Content-Type | application/json |
Corpo do pedido
Exemplo de um pedido.criado com dois itens, cupom e entrega:
{
"event": "pedido.criado",
"sent_at": "2026-10-25T14:32:08.512Z",
"store": {
"id": "6f1c2a9e-4b7d-4e2a-9c51-0d8f3b7a1e22",
"slug": "loja-da-ana",
"name": "Loja da Ana"
},
"order": {
"id": "b3e0d7c4-1a2f-4c8e-9d6b-5f4a3c2e1d0f",
"number": 128,
"created_at": "2026-10-25T14:32:07.904Z",
"status": "novo",
"customer": {
"name": "Mariana Souza",
"whatsapp": "5562991234567"
},
"items": [
{
"product_id": "2c9d8e7f-6a5b-4c3d-8e1f-0a9b8c7d6e5f",
"sku": "VES-014-M-AZUL",
"name": "Vestido midi de linho",
"options": {
"Tamanho": "M",
"Cor": "Azul"
},
"quantity": 1,
"unit_price": 189.9,
"total": 189.9
},
{
"product_id": "7a6b5c4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d",
"sku": "BRI-002",
"name": "Brinco de argola dourado",
"options": {},
"quantity": 2,
"unit_price": 39.9,
"total": 79.8
}
],
"subtotal": 269.7,
"discount": 26.97,
"delivery_fee": 15,
"total": 257.73,
"coupon_code": "BEMVINDA10",
"payment": {
"method": "online",
"status": "aguardando",
"paid_at": null
},
"fulfillment": {
"type": "entrega",
"address": "Rua 15, 230, Setor Oeste, Goiânia - GO, 74120-050"
}
}
}| Campo | Descrição |
|---|---|
event | Nome do evento. |
sent_at | Quando o evento foi gerado (ISO 8601, UTC). Nas novas tentativas o corpo é o mesmo, então este valor não muda. |
store | Loja: id, slug (o final do endereço da vitrine) e name. |
order.id | Identificador do pedido (UUID). Use para casar eventos do mesmo pedido. |
order.number | Número sequencial do pedido na loja, o mesmo que o lojista vê no painel. |
order.created_at | Quando o pedido foi feito (ISO 8601, UTC). |
order.status | Status do pedido no painel. Valores na próxima seção. |
order.customer | name e whatsapp (só dígitos, com DDI e DDD). |
order.items[].product_id | Identificador do produto no LojaBio. Pode ser null se o produto foi apagado depois do pedido. |
order.items[].sku | Código (SKU) cadastrado no produto. Se a combinação escolhida (tamanho, cor) tem código próprio, vem o da combinação. É o código de quando o pedido foi feito. null quando o lojista não cadastrou código. |
order.items[].name | Nome do produto no momento do pedido. |
order.items[].options | Opções escolhidas, como {"Tamanho": "M"}. Objeto vazio quando o produto não tem opções. |
order.items[].quantity | Quantidade (inteiro, de 1 a 99). |
order.items[].unit_price | Preço unitário cobrado, já com promoção ou atacado. |
order.items[].total | unit_price × quantity, com duas casas. |
order.subtotal | Soma dos itens. |
order.discount | Desconto do cupom. 0 sem cupom. |
order.delivery_fee | Taxa de entrega. 0 na retirada ou sem entrega. |
order.total | subtotal − discount + delivery_fee. |
order.coupon_code | Código do cupom usado, ou null. |
order.payment | method, status e paid_at (ISO 8601 ou null). |
order.fulfillment | type (retirada ou entrega) e address (texto livre, só na entrega). null quando a loja não usa retirada nem entrega. |
Valores em reais, como número com até duas casas decimais (não em centavos). Campos novos podem ser acrescentados; ignore os que não conhecer.
Valores possíveis
payment.status
| Valor | Significado |
|---|---|
nao_se_aplica | O pagamento não passa pelo LojaBio (combinado com o lojista). |
aguardando | Cobrança criada, ainda sem pagamento. |
pago | Pagamento confirmado. paid_at traz o momento. |
expirado | O Pix ou o link de pagamento venceu sem pagamento. |
estornado | O pagamento foi devolvido ao cliente. |
payment.method
| Valor | Significado |
|---|---|
combinar | Cliente e lojista combinam o pagamento pelo WhatsApp. |
pix | Pix na chave da loja, conferido pelo lojista. |
pix_auto | Pix com confirmação automática. |
online | Pagamento online pelo serviço que a loja conectou (Asaas, Mercado Pago ou InfinitePay). |
order.status
| Valor | Significado |
|---|---|
novo | Pedido recém-chegado. |
visto | O lojista abriu o pedido. |
concluido | Pedido entregue ou finalizado. |
canceled | Pedido cancelado. |
Verificar a assinatura
Quem usa Make, Zapier ou Pluga pode pular esta parte. Quem recebe o webhook no próprio servidor deve conferir a assinatura antes de confiar no conteúdo:
- Leia o corpo cru da requisição, exatamente como chegou, sem converter o JSON.
- Monte o texto
${X-LojaBio-Timestamp}.${corpo}: o timestamp, um ponto e o corpo. - Calcule o HMAC-SHA256 desse texto com o segredo da loja e escreva em hexadecimal minúsculo, com o prefixo
sha256=. - Compare com
X-LojaBio-Signatureusando comparação de tempo constante. - Recuse timestamps com mais de 5 minutos de diferença do seu relógio. Isso barra o reenvio de uma requisição antiga capturada.
import { createHmac, timingSafeEqual } from 'node:crypto'
import express from 'express'
const SEGREDO = process.env.LOJABIO_WEBHOOK_SECRET
const app = express()
// Corpo cru: a assinatura é calculada sobre os bytes exatos que chegaram.
// Se o framework converter o JSON antes, a conta não fecha.
app.post('/webhooks/lojabio', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-LojaBio-Timestamp') ?? ''
const assinatura = req.get('X-LojaBio-Signature') ?? ''
const corpo = req.body.toString('utf8')
const esperado =
'sha256=' + createHmac('sha256', SEGREDO).update(`${timestamp}.${corpo}`).digest('hex')
const valida =
assinatura.length === esperado.length &&
timingSafeEqual(Buffer.from(assinatura), Buffer.from(esperado))
const recente = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300
if (!valida || !recente) return res.status(401).end()
const entrega = req.get('X-LojaBio-Delivery')
const evento = JSON.parse(corpo)
// Guarde `entrega` e ignore se ela já foi processada (é a mesma em todas
// as tentativas). Responda logo e deixe o trabalho pesado para uma fila.
res.status(200).end()
})
app.listen(3000)<?php
$segredo = getenv('LOJABIO_WEBHOOK_SECRET');
$corpo = file_get_contents('php://input'); // corpo cru, antes de qualquer json_decode
$timestamp = $_SERVER['HTTP_X_LOJABIO_TIMESTAMP'] ?? '';
$assinatura = $_SERVER['HTTP_X_LOJABIO_SIGNATURE'] ?? '';
$esperado = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $corpo, $segredo);
if (!hash_equals($esperado, $assinatura) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
$entrega = $_SERVER['HTTP_X_LOJABIO_DELIVERY'] ?? '';
$evento = json_decode($corpo, true);
// Se $entrega já foi processada, responda 200 e pare: é uma nova tentativa.
// Senão, registre e processe. Responda em menos de 10 segundos.
http_response_code(200);Resposta e novas tentativas
Responda com qualquer status 2xx em até 10 segundos. O conteúdo da resposta é ignorado. Qualquer outro status (inclusive 3xx), erro de conexão ou demora acima de 10 segundos conta como falha.
A primeira tentativa sai na hora do evento. Se falhar, o LojaBio tenta de novo até completar 6 tentativas, com espera crescente entre elas:
| Tentativa | Quando |
|---|---|
| 1ª | Na hora do evento. |
| 2ª | 1 minuto depois da falha. |
| 3ª | 5 minutos depois. |
| 4ª | 30 minutos depois. |
| 5ª | 2 horas depois. |
| 6ª | 6 horas depois. Se falhar, a entrega fica como Falhou e não é reenviada. |
Os reenvios rodam numa fila verificada a cada 5 minutos, então cada espera pode passar alguns minutos do previsto. Do primeiro envio à última tentativa são cerca de 9 horas. O lojista vê cada envio, o status HTTP e o erro em Integrações → Últimos envios.
2xx assim que validar e guardar o evento, e faça o resto (criar o pedido no ERP, emitir nota) depois. Se o seu processamento passar de 10 segundos, o LojaBio considera falha e manda de novo.Entrega repetida
Uma mesma entrega pode chegar mais de uma vez: quando a sua resposta se perde na rede, ou quando ela chega depois dos 10 segundos. Use X-LojaBio-Delivery como chave: se já processou aquele identificador, responda 200 e não faça nada.
Eventos diferentes do mesmo pedido têm identificadores de entrega diferentes. Para não criar o pedido duas vezes no ERP, crie em um evento só (pedido.criado, ou pedido.pago se a loja só fatura o que foi pago) e use os outros para atualizar, casando por order.id ou order.number.
LojaBio no Bling pelo Make
O Make recebe o webhook e cria o pedido de venda no Bling, sem programar. Antes de começar, cadastre no LojaBio o Código (SKU) de cada produto igual ao código do mesmo produto no Bling. É por esse código que o Bling reconhece o item. Produto com tamanho e cor pode ter um código por combinação, na grade de variações.
- Crie uma conta em make.com e um cenário novo.
- No primeiro módulo, procure o app Webhooks e escolha Custom webhook. Crie um webhook novo, dê um nome (por exemplo, “Pedidos LojaBio”) e salve. O Make mostra um endereço que começa com
https://hook.: copie. - No LojaBio, abra Painel → Integrações → Integração com ERP, cole o endereço e clique em Salvar. Com o módulo do Make esperando dados, clique em Enviar teste. O Make recebe o pedido de exemplo e passa a conhecer todos os campos.
- Depois do webhook, adicione um filtro com a condição
eventigual apedido.criado(oupedido.pago, se a loja só lança pedido pago no Bling). Sem o filtro, cada mudança de status criaria um pedido novo no Bling, e o pedido de teste também entraria. - Adicione o app Bling com o módulo Create a Sales Order e conecte a sua conta do Bling. A conexão pede um aplicativo cadastrado na área de desenvolvedor do Bling (Central de Extensões → Área do Integrador), de onde saem o Client Id e o Client Secret.
- Ligue os campos: cliente com
order.customer.nameeorder.customer.whatsapp; número do pedido na loja comorder.number; data comorder.created_at; nos itens, código comitems[].sku, quantidade comitems[].quantitye valor comitems[].unit_price; desconto, frete e total comorder.discount,order.delivery_feeeorder.total. Se o módulo pedir um item por vez, passeorder.itemspelo Iterator e junte de volta com o Array aggregator. - Clique em Run once, mande mais um teste pelo LojaBio (ou faça um pedido de verdade na vitrine) e confira o pedido no Bling. Depois, ligue o cenário e salve.
Sem módulo de resposta, o Make responde 200 com o texto “Accepted” assim que coloca o pedido na fila do cenário. Para o LojaBio a entrega está feita. Se o Bling recusar o pedido depois (código de produto que não existe, cliente sem dado obrigatório), o erro aparece no histórico do cenário no Make, não no painel do LojaBio.
O Make tem plano gratuito, com 1.000 créditos por mês, e cada módulo executado consome crédito. Confira na página de preços do Make se o plano cobre o volume de pedidos da loja. Um webhook do Make que fica mais de 5 dias sem cenário ligado é desativado e passa a responder 410: nesse caso, os envios aparecem como falha no LojaBio.
Zapier e Pluga
Zapier: o gatilho é o app Webhooks by Zapier, evento Catch Hook. Ele é um app Premium: só funciona em plano pago do Zapier ou durante o período de teste. O resto do caminho é igual ao do Make: filtro pelo evento, depois a ação de criar pedido no ERP.
Pluga: a Pluga, em português, tem um gatilho de Webhook que gera um endereço único para cada automação, e o Bling está entre as ferramentas dela. Confira com a Pluga qual plano inclui o gatilho de Webhook.