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.

EventoQuando é enviado
pedido.criadoO cliente fechou o pedido na vitrine.
pedido.pagoO pagamento foi confirmado: pelo serviço de pagamento conectado (Asaas, Mercado Pago, InfinitePay) ou pelo lojista, ao marcar um Pix como recebido.
pedido.statusO 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.
testeDisparado 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çalhoConteúdo
X-LojaBio-EventNome do evento, igual ao campo event.
X-LojaBio-DeliveryIdentificador da entrega (UUID). É o mesmo em todas as tentativas do mesmo envio.
X-LojaBio-TimestampMomento da tentativa, em segundos desde 1970 (Unix). Muda a cada tentativa.
X-LojaBio-Signaturesha256= seguido do HMAC-SHA256 em hexadecimal minúsculo. Veja como conferir abaixo.
User-AgentLojaBio-Webhook/1
Content-Typeapplication/json

Corpo do pedido

Exemplo de um pedido.criado com dois itens, cupom e entrega:

POST · application/json
{
  "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"
    }
  }
}
CampoDescrição
eventNome do evento.
sent_atQuando o evento foi gerado (ISO 8601, UTC). Nas novas tentativas o corpo é o mesmo, então este valor não muda.
storeLoja: id, slug (o final do endereço da vitrine) e name.
order.idIdentificador do pedido (UUID). Use para casar eventos do mesmo pedido.
order.numberNúmero sequencial do pedido na loja, o mesmo que o lojista vê no painel.
order.created_atQuando o pedido foi feito (ISO 8601, UTC).
order.statusStatus do pedido no painel. Valores na próxima seção.
order.customername e whatsapp (só dígitos, com DDI e DDD).
order.items[].product_idIdentificador do produto no LojaBio. Pode ser null se o produto foi apagado depois do pedido.
order.items[].skuCó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[].nameNome do produto no momento do pedido.
order.items[].optionsOpções escolhidas, como {"Tamanho": "M"}. Objeto vazio quando o produto não tem opções.
order.items[].quantityQuantidade (inteiro, de 1 a 99).
order.items[].unit_pricePreço unitário cobrado, já com promoção ou atacado.
order.items[].totalunit_price × quantity, com duas casas.
order.subtotalSoma dos itens.
order.discountDesconto do cupom. 0 sem cupom.
order.delivery_feeTaxa de entrega. 0 na retirada ou sem entrega.
order.totalsubtotal − discount + delivery_fee.
order.coupon_codeCódigo do cupom usado, ou null.
order.paymentmethod, status e paid_at (ISO 8601 ou null).
order.fulfillmenttype (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

ValorSignificado
nao_se_aplicaO pagamento não passa pelo LojaBio (combinado com o lojista).
aguardandoCobrança criada, ainda sem pagamento.
pagoPagamento confirmado. paid_at traz o momento.
expiradoO Pix ou o link de pagamento venceu sem pagamento.
estornadoO pagamento foi devolvido ao cliente.

payment.method

ValorSignificado
combinarCliente e lojista combinam o pagamento pelo WhatsApp.
pixPix na chave da loja, conferido pelo lojista.
pix_autoPix com confirmação automática.
onlinePagamento online pelo serviço que a loja conectou (Asaas, Mercado Pago ou InfinitePay).

order.status

ValorSignificado
novoPedido recém-chegado.
vistoO lojista abriu o pedido.
concluidoPedido entregue ou finalizado.
canceledPedido 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:

  1. Leia o corpo cru da requisição, exatamente como chegou, sem converter o JSON.
  2. Monte o texto ${X-LojaBio-Timestamp}.${corpo}: o timestamp, um ponto e o corpo.
  3. Calcule o HMAC-SHA256 desse texto com o segredo da loja e escreva em hexadecimal minúsculo, com o prefixo sha256=.
  4. Compare com X-LojaBio-Signature usando comparação de tempo constante.
  5. Recuse timestamps com mais de 5 minutos de diferença do seu relógio. Isso barra o reenvio de uma requisição antiga capturada.
Node.js (Express)
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
<?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:

TentativaQuando
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.

Responda 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.

  1. Crie uma conta em make.com e um cenário novo.
  2. 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.
  3. 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.
  4. Depois do webhook, adicione um filtro com a condição event igual a pedido.criado (ou pedido.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.
  5. 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.
  6. Ligue os campos: cliente com order.customer.name e order.customer.whatsapp; número do pedido na loja com order.number; data com order.created_at; nos itens, código com items[].sku, quantidade com items[].quantity e valor com items[].unit_price; desconto, frete e total com order.discount, order.delivery_fee e order.total. Se o módulo pedir um item por vez, passe order.items pelo Iterator e junte de volta com o Array aggregator.
  7. 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.
O app Bling do Make é mantido por um desenvolvedor parceiro, não pelo Make nem pelo Bling, e segue os termos desse desenvolvedor. Confira os módulos e eventuais custos na página do app antes de montar o cenário.

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.

Para ligar o webhook na sua loja: Abrir Integrações no painel