PT EN
Entrar Cadastrar →
Padrão Open Delivery (ABRASEL)

Integração Open Delivery

Documentação técnica completa da nossa API Open Delivery. A integração segue a especificação da ABRASEL — quem já implementou o padrão em outra plataforma logística reutiliza o mesmo código, mudando só URL base, client_id e client_secret.

Introdução

A API Open Delivery do Entregas Expressas permite que softwares baseados na especificação Open Delivery se integrem à nossa plataforma sem novos desenvolvimentos. O fluxo típico:

  1. O estabelecimento (cliente final) gera client_id + client_secret no painel dele e te envia.
  2. Você troca essas credenciais por um access token via OAuth2 (client credentials).
  3. Você cria entregas, simula preços, marca pronto pra coleta, cancela — tudo com o token no header.
  4. Recebe webhooks em cada mudança de status (aceito, coletado, entregue, etc.).

Trocar informações com o estabelecimento

🔁 Você envia para o estabelecimento

🔐 O estabelecimento envia para você

Cada estabelecimento gera um par próprio de credenciais — NÃO compartilhe credenciais entre estabelecimentos diferentes. O cliente preenche tudo em Integrações > Open Delivery e marca “ativar integração”.

URL base & APP-ID

URL base padrão:

https://entregasexpressas.com.br/api/opendelivery

Nosso APP-ID:

d3e3eaf2-e585-4177-962a-7be998efab8e
Apps oficialmente parceiros recebem uma URL base própria. Os endpoints continuam idênticos — muda só o host. Solicite a publicação via aba Visão geral > Enviar para review do seu app.

🔑 Autenticação

Padrão OAuth 2.0 client_credentials. Gera um access token JWT que vale por 24h (expires_in: 86400). Use o token no header Authorization: Bearer <token> de todas as chamadas seguintes.

POST /oauth/token 200

Request body

{
  "grant_type": "client_credentials",
  "client_id": "<client_id_do_estabelecimento>",
  "client_secret": "<client_secret_do_estabelecimento>"
}

Response 200

{
  "access_token": "eyJhbGciOiJIUzI1NiI...",
  "token_type": "bearer",
  "expires_in": 86400
}

Exemplo cURL

curl -X POST https://entregasexpressas.com.br/api/opendelivery/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "...",
    "client_secret": "..."
  }'

📊 Simular entrega (disponibilidade e preço)

Antes de criar uma entrega, simule preço, ETAs e disponibilidade de veículos. Útil pra mostrar o valor da entrega no checkout do estabelecimento antes do consumidor finalizar.

POST /v1/logistics/availability 200 400, 404 503

Request body — campos principais

CampoTipoDescrição
pickupAddress obrigatórioobject · AddressLogisticsEndereço de coleta no estabelecimento.
deliveryAddress obrigatórioobject · AddressLogisticsEndereço de entrega ao consumidor.
vehicle obrigatórioobject · VehicleTipos de veículo aceitos.
limitTimes obrigatórioobjectpickupLimit e deliveryLimit em minutos.
totalOrderPrice obrigatórioobject · PriceValor total do pedido apresentado ao consumidor.
totalWeight obrigatóriointegerPeso total em gramas.
returnToMerchant obrigatóriobooleanEntregador volta ao ponto de coleta após entregar?
canCombine obrigatóriobooleanPermitir agrupar com outros pedidos do mesmo entregador?
merchant opcionalobjectid + name do estabelecimento.
orderDeliveryFee opcionalobject · PriceTaxa de entrega já cobrada do consumidor.
packageVolume opcionalnumberVolume em m³.
packageQuantity opcionalintegerQuantidade de volumes.
additionalPricePercentual opcionalnumber% acima do preço calculado que aceita pagar (ex.: 50 = 50%).
specialInstructions opcionalstringInstruções livres para o entregador.
onlinePayment opcionalbooleanPagamento já foi feito no app do estabelecimento?

Request body — exemplo

{
  "merchant": {
    "id": "22815773000169-dbc7e35a-c936-4665-9e13-eb55eb8b6824",
    "name": "Pizza Plaza"
  },
  "pickupAddress": {
    "country": "BR", "state": "BR-SP", "city": "São Paulo",
    "district": "Moema", "street": "Av. Plaza", "number": "100",
    "postalCode": "20111-000", "complement": "",
    "latitude": -23.54809, "longitude": -46.63638
  },
  "deliveryAddress": {
    "country": "BR", "state": "BR-SP", "city": "São Paulo",
    "district": "Vila Mariana", "street": "R. Domingos de Morais", "number": "1267",
    "postalCode": "04009-002", "complement": "Apto 31",
    "latitude": -23.59045, "longitude": -46.63876
  },
  "vehicle": {
    "type": ["MOTORBIKE_BAG"],
    "container": "THERMIC",
    "containerSize": "MEDIUM"
  },
  "limitTimes": {
    "pickupLimit": 20,
    "deliveryLimit": 45,
    "orderCreatedAt": "2026-05-17T14:32:00Z"
  },
  "totalOrderPrice": { "value": 89.90, "currency": "BRL" },
  "totalWeight": 1200,
  "returnToMerchant": false,
  "canCombine": true,
  "onlinePayment": true
}

Response 200 — campos retornados

CampoTipoDescrição
deliveryPriceobjectValor calculado da entrega.
vehiclesobjectavailableVehicles, vehiclesAvailable, nextAvailableVehicle (em minutos).
ETAsobjectTempos previstos (pickup & delivery, online ou offline).
{
  "deliveryPrice": { "value": 12.50, "currency": "BRL" },
  "vehicles": {
    "availableVehicles": 4,
    "nextAvailableVehicle": 0
  },
  "ETAs": {
    "updateMethod": "ONLINE",
    "pickupEtaInMinutes": 8,
    "pickupEtaDatetime": "2026-05-17T14:40:00Z",
    "deliveryEtaInMinutes": 32,
    "deliveryEtaDatetime": "2026-05-17T15:04:00Z",
    "maxDeliveryTime": "2026-05-17T15:17:00Z"
  }
}

📦 Criar nova entrega

Registra uma nova solicitação de entrega. Retorna 202 Accepted — o aceite final chega via webhook (ACCEPTED ou REJECTED).

POST /v1/logistics/delivery 202 400, 401, 403, 404, 422 503

Request body — campos obrigatórios

Todos os campos do /availability mais:

CampoTipoDescrição
orderId obrigatóriostring (UUID)ID do pedido gerado pelo estabelecimento.
orderDisplayId obrigatóriostringID visível ao consumidor (ex.: 96b41025).
merchant obrigatórioobjectid e name do estabelecimento.
customerName obrigatóriostringNome do destinatário.
payments obrigatórioobjectVeja abaixo.
customerPhone opcionalstringTelefone do destinatário (precisa de consentimento).
metadata opcionalobjectCódigo de retirada (metadata.pickUpCode) e dados do marketplace de origem (metadata.partnerData). Veja Pedidos do iFood.
notifyReadyForPickup opcionalbooleanEsperar o endpoint /readyForPickup antes de chamar o entregador.
confirmationCodeRequired opcionalbooleanExigir código de confirmação na entrega.
items opcionalarrayLista { name, quantity } dos produtos.
combinedOrdersIds opcionalarray of UUIDsPedidos já em curso que podem ser agrupados.

Objeto payments

CampoTipoDescrição
methodenumOFFLINEONLINE
wirelessPosbooleanEntregador leva maquininha?
offlineMethod[].typeenumCREDITDEBITMEAL_VOUCHERFOOD_VOUCHERPIXCASHCREDIT_DEBITOTHER
offlineMethod[].amountobject · PriceValor pago naquele método.
changeobject · PriceTroco (se type=CASH).

Response 202 — request aceita pra processamento

{
  "deliveryId": "8f3e9c20-aa14-4f5d-92b2-9bca6e0a3e10",
  "orderId": "52122852-6626-11ed-9022-0242ac120002",
  "status": "PENDING",
  "createdAt": "2026-05-17T14:32:05Z"
}
O status final (ACCEPTED ou REJECTED) chega via webhook. Caso seja REJECTED, o evento traz rejectionInfo.reason (PRICE_EXCEEDED, VEHICLE_NOT_AVAILABLE, NO_DELIVERYPERSON_AVAILABLE, DOES_NOT_MEET_REQUESTED_TIMES, REGION_NOT_SERVED, OTHER).

🍔 Pedidos do iFood (e de outros marketplaces)

Quando o pedido nasceu num marketplace e o seu sistema repassa a entrega para nós, informe a origem e os códigos dele no objeto metadata de POST /v1/logistics/delivery. É com eles que o entregador finaliza a entrega no iFood e que o estabelecimento encontra o pedido no painel dele.

Campos de metadata

CampoTipoDescrição
metadata.partnerData.orderPartnerNamestringNome do marketplace de origem. Para o iFood, envie exatamente iFood (com essa grafia de maiúsculas e minúsculas). Esse valor identifica o pedido como do iFood.
metadata.partnerData.orderDisplayIdstringNúmero do pedido no iFood, como aparece para a loja e para o consumidor.
metadata.partnerData.phoneNumberstringTelefone de contato do iFood com o localizador do pedido. O localizador tem 8 dígitos e precisa vir precedido de localizador, por exemplo 0800 200 5011, localizador: 93898428.
metadata.pickUpCodestringCódigo de retirada que o entregador apresenta na coleta.

O que fazemos com cada código

Exemplo

{
  "orderId": "52122852-6626-11ed-9022-0242ac120002",
  "orderDisplayId": "4821",
  "merchant": { "id": "loja-123", "name": "Hamburgueria Central" },
  ...
  "metadata": {
    "pickUpCode": "7391",
    "partnerData": {
      "orderPartnerName": "iFood",
      "orderDisplayId": "6512",
      "phoneNumber": "0800 200 5011, localizador: 93898428"
    }
  }
}
Receber pedidos do iFood depende de o estabelecimento ter habilitado essa opção na integração. Quando não está habilitada, a chamada é respondida com 202 e "event": "REJECTED", e a entrega não é criada. Pedido sem metadata.partnerData é tratado como pedido próprio da loja.

🛎️ Informar pronto para coleta

Use quando o pedido fica pronto antes do tempo previsto e você quer antecipar a chamada do entregador. Só obrigatório se você setou notifyReadyForPickup: true ao criar a entrega.

POST /v1/logistics/readyForPickup/{orderId} 200 400, 401, 403, 404, 422 503

Sem body. orderId na URL é o mesmo UUID enviado em POST /v1/logistics/delivery.

❌ Cancelar entrega

Pode ser chamado a qualquer momento. Se o entregador já aceitou, a operadora pode cobrar taxa adicional conforme política. Se o pedido estiver em entrega combinada, ele é desvinculado (os outros pedidos seguem normalmente).

POST /v1/logistics/cancel/{orderId} 202 400, 401, 403, 404, 422 503

Request body

CampoTipoDescrição
reason obrigatórioenumCONSUMER_CANCELLATION_REQUESTEDNO_SHOWPROBLEM_AT_MERCHANTHIGH_ACCEPTANCE_TIMEINCORRECT_ORDER_OR_PRODUCT_PICKUPPROBLEM_RESOLUTIONDISCOMBINE_ORDEROTHER
action opcionalenumRETURN_TO_STORECANCEL_DELIVERY
message opcionalstringTexto livre com detalhes.

Response 202

{
  "additionalCharges": false
}

additionalCharges indica se o cancelamento gera cobrança adicional.

🔍 Detalhes da entrega

Consulta o estado completo de uma entrega — última tentativa processada (em caso de re-tentativas).

GET /v1/logistics/delivery/{orderId} 200 400, 404 503

Retorna um objeto com todos os campos do DeliveryOrder (que você enviou), mais o histórico de eventos, dados do entregador (deliveryMan) e localização (geoLocalization) quando disponíveis.

📬 Webhook de status

A cada mudança de estado da entrega, fazemos um POST para a Webhook URL configurada pelo estabelecimento. Responda com HTTP 204 em até 10s — qualquer outra coisa dispara retry com backoff.

Importante: enviamos o POST para a Webhook URL exatamente como cadastrada, sem concatenar nenhum sufixo de rota. Se o seu endpoint espera receber os eventos em /v1/newLogisticEvent, cadastre esse caminho já incluído na própria Webhook URL (ex.: https://seu-dominio/v1/opendelivery/webhook/v1/newLogisticEvent).

POST {webhook-url-cadastrada} 204 esperado

Headers obrigatórios

HeaderConteúdo
X-App-IdNosso APP-ID (UUID) — sempre o mesmo, identifica o serviço logístico.
X-App-MerchantIdO merchant.id que vocês compartilharam.
X-App-SignatureHMAC-SHA256 do body usando o client_secret como chave. Valide pra garantir autenticidade.

Payload — campos principais

O tipo, o horário e a mensagem do evento vão aninhados dentro do objeto event (não no topo). Os campos abaixo são sempre enviados; os campos operacionais (vehicle, deliveryPrice, eta, deliveryPerson) só aparecem em eventos de andamento — não em PENDING, REJECTED nem CANCELLED.

A estrutura segue o schema oficial newLogisticEvent do OpenDelivery. Os campos operacionais (vehicle, deliveryPrice, eta, deliveryPerson) são condicionais: acompanham eventos de andamento, mas não PENDING, REJECTED nem CANCELLED.

CampoTipoObrig.Descrição
deliveryIdstring <UUID>obrigatórioId da entrega gerado pelo serviço logístico.
orderIdstring <uuid>obrigatórioO orderId que vocês enviaram na criação (external id do endereço).
orderDisplayIdstringobrigatórioId do pedido exibido pro cliente na aplicação de pedidos.
merchant.idstring [36..100]obrigatórioIdentificador único do merchant. Ecoamos o valor enviado na criação daquele endereço.
merchant.namestring <=500obrigatórioNome público do merchant.
event.typeenumobrigatório PENDINGACCEPTEDREJECTED PICKUP_ONGOINGARRIVED_AT_MERCHANTORDER_PICKED DELIVERY_ONGOINGARRIVED_AT_CUSTOMERORDER_DELIVERED RETURNING_TO_MERCHANTRETURNED_TO_MERCHANT DELIVERY_FINISHEDCANCELLED
event.datetimestring <date-time>obrigatórioQuando o evento ocorreu (ISO 8601 UTC).
event.messagestringopcionalMensagem livre com detalhes do evento.
event.rejectionInfoobjectopcionalAcompanha eventos REJECTED/CANCELLED. Traz reason com o motivo.
problemarray<object>opcionalLista de problemas da entrega. Cada item: { reason(enum), message?, datetime, canFinishDeliver(bool), needMerchantAction(bool) }.
customerNamestringobrigatórioNome do cliente do endereço.
vehicleobjectcondicional{ type(enum), container(enum), containerSize?(enum), instruction?(string) }.
deliveryPriceobjectcondicional{ price: { value, currency }, pricingList(NORMAL|DYNAMIC), additionalPricePercentual }.
etaobjectcondicional{ updateMethod(OFFLINE|ONLINE), pickupEtaInMinutes, pickupEtaDatetime, deliveryEtaInMinutes, deliveryEtaDatetime, maxDeliveryTime }.
deliveryPersonobjectcondicionalQuando há entregador atribuído. { id, name, pictureURL?, phone? }.
externalTrackingURLstring <URI>opcionalLink público de acompanhamento da entrega.
combinedOrdersIdsarray<uuid>opcionalIds de outros pedidos do mesmo merchant levados pelo mesmo entregador.

Exemplo — evento de aceite

{
  "deliveryId": "32a20413-d5da-4b5d-a7f1-7a36e7f3495f",
  "orderId": "2003",
  "orderDisplayId": "12",
  "merchant": { "id": "2001", "name": "Loja Exemplo" },
  "event": {
    "type": "ACCEPTED",
    "datetime": "2026-05-17T14:32:18.000000Z",
    "message": "Pedido aceito pelo serviço de entrega"
  },
  "problem": [],
  "customerName": "João da Silva",
  "vehicle": {
    "type": "MOTORBIKE_BAG",
    "container": "THERMIC",
    "containerSize": "MEDIUM",
    "instruction": "N/A"
  },
  "deliveryPrice": {
    "price": { "value": 12.5, "currency": "BRL" },
    "pricingList": "NORMAL",
    "additionalPricePercentual": 0
  },
  "eta": {
    "updateMethod": "OFFLINE",
    "pickupEtaInMinutes": 15,
    "pickupEtaDatetime": "2026-05-17T14:47:18.000000Z",
    "deliveryEtaInMinutes": 30,
    "deliveryEtaDatetime": "2026-05-17T15:02:18.000000Z",
    "maxDeliveryTime": "2026-05-17T16:02:18.000000Z"
  },
  "deliveryPerson": {
    "id": "1138",
    "name": "Carlos Silva",
    "phone": "5511999990000",
    "pictureURL": "https://cdn.exemplo.com/couriers/1138.jpg"
  },
  "externalTrackingURL": "https://seu-dominio/tracking/32a20413-d5da-4b5d-a7f1-7a36e7f3495f",
  "combinedOrdersIds": []
}

Exemplo — evento de rejeição

{
  "deliveryId": "32a20413-d5da-4b5d-a7f1-7a36e7f3495f",
  "orderId": "2003",
  "orderDisplayId": "12",
  "merchant": { "id": "2001", "name": "Loja Exemplo" },
  "event": {
    "type": "REJECTED",
    "datetime": "2026-05-17T14:33:02.000000Z",
    "message": "Pedido fora do raio de entrega",
    "rejectionInfo": { "reason": "REGION_NOT_SERVED" }
  },
  "problem": [],
  "customerName": "João da Silva",
  "externalTrackingURL": "https://seu-dominio/tracking/32a20413-d5da-4b5d-a7f1-7a36e7f3495f",
  "combinedOrdersIds": []
}

Validando a assinatura (Node.js)

const crypto = require('crypto');
function validateSignature(rawBody, headerSignature, clientSecret) {
  const expected = crypto
    .createHmac('sha256', clientSecret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(headerSignature, 'hex')
  );
}

Schemas auxiliares

AddressLogistics

CampoTipoObs.
country obrigatóriostringISO 3166-1 alpha-2 (ex.: BR).
state obrigatóriostringISO 3166-2 (ex.: BR-SP).
city obrigatóriostring
district obrigatóriostringBairro.
street obrigatóriostring
number obrigatóriostring
postalCode obrigatóriostringCEP, com ou sem hífen.
complement obrigatóriostringPode ser vazio mas o campo deve existir.
reference opcionalstringPonto de referência.
latitudenumber-90 a 90.
longitudenumber-180 a 180.

Vehicle

CampoTipoObs.
type obrigatórioarray<enum> Tipos aceitos: MOTORBIKE_BAGMOTORBIKE_BOXCARBICYCLESCOOTERVUC. Mande mais de um pra deixar a operadora escolher.
container obrigatórioenumNORMALTHERMIC
containerSize opcionalenumSMALLMEDIUMLARGEEXTRA_LARGE
instruction opcionalstringInstruções especiais ao entregador.

Price

CampoTipoObs.
value obrigatórionumberAceita até 4 casas decimais.
currency obrigatóriostringUse BRL.

Códigos de erro

HTTPQuando
200/202Sucesso. 202 indica processamento assíncrono (estado final via webhook).
400Request mal formada — JSON inválido ou faltando campos obrigatórios.
401Token ausente, inválido ou expirado.
403Token válido mas sem permissão para o recurso.
404orderId não encontrado.
422Conteúdo válido mas regra de negócio impede (ex.: cancelar pedido já entregue).
503Serviço temporariamente indisponível — repita com backoff.

Usar o painel do desenvolvedor

Todo o ciclo de desenvolvimento acontece dentro do portal: você cria o app, recebe credenciais sandbox, configura webhook, inspeciona requests e simula pedidos sem precisar coordenar com entregador real. Os passos abaixo seguem a ordem natural do trabalho.

1. Criar um app OpenDelivery

No painel, clique em Aplicativos > Novo aplicativo e escolha o tipo OpenDelivery. Assim que o app é criado, provisionamos automaticamente um estabelecimento sandbox numa praça dedicada, com a integração já ativada. Não há etapa manual de “conectar com cliente” — o sandbox existe pra você desenvolver sem depender de cliente real.

2. Onde pegar o client_id e client_secret de teste

  1. Abra o app no painel.
  2. Vá na aba Credenciais.
  3. Copie os campos client_id e client_secret — botão de copiar ao lado de cada um.
  4. Use exatamente esses valores no payload de POST /oauth/token da seção de autenticação.
Em sandbox, esse par de credenciais é FIXO e representa o estabelecimento de teste já provisionado. Em produção (após homologação), cada estabelecimento gera o próprio par direto no painel dele em Integrações > Open Delivery, e cada par representa um estabelecimento diferente.

Precisa regerar o client_secret? Botão Regerar credenciais na própria aba Credenciais. O par antigo deixa de funcionar imediatamente — atualize a sua aplicação antes de regerar.

3. Configurar a URL de webhook

  1. Aba Webhook do app.
  2. No campo URL de webhook, cole a URL HTTPS onde sua aplicação recebe eventos (formato detalhado em Webhook de status).
  3. Marque “Permitir que o estabelecimento sobrescreva esta URL” se você quer dar essa opção ao cliente final — em produção, alguns estabelecimentos têm webhook próprio (sistema de PDV, ERP). Se você deixar desmarcado, a URL fica fixa para todos.
  4. Clique em Salvar. A mudança vale imediatamente — não precisamos fazer deploy.

4. Inspecionar requests recebidas

A aba Inspeção mostra cada request que a nossa plataforma fez para a sua API (e, em alguns casos, o que sua API respondeu). Para cada uma:

Use isso pra debugar OAuth, validar payloads, achar a causa de um 4xx que sua aplicação retornou sem precisar mexer nos seus logs.

5. Simular pedidos e disparar webhooks

Aba Pedidos teste: lista os pedidos criados via API no estabelecimento sandbox. Para cada um, você consegue avançar o status manualmente (aceitar, atribuir entregador, marcar como coletado, entregue, ou disparar uma rejeição). Cada mudança envia o webhook real para a sua URL — você testa os handlers do seu lado sem precisar coordenar entregador real ou esperar minutos.

6. Convidar outros devs do time

  1. Aba Membros > Convidar.
  2. Digite o email do colega.
  3. Se ele já tem conta no portal, recebe um link pra aceitar. Se não tem, recebe link pra se cadastrar já vinculado ao seu app.
  4. Membros enxergam tudo: credenciais, webhook, inspeção, simulação.

7. Ajustar nome, descrição, logo e site do app

Aba Configurações: nome do app, descrição clara do que ele faz, site público, e logo (PNG/SVG). Esses dados aparecem na vitrine pública e na tela onde o estabelecimento ativa a sua integração — descrição clara e logo nítida têm efeito direto na conversão. Os três campos (descrição, site do app, logo) são obrigatórios para submeter o app à homologação.

Enviar para homologação

Depois que sua implementação estiver funcionando em sandbox, você submete o app à homologação da equipe Plataforma Entregas Expressas. Quando aprovado, o app fica visível para os +30 mil estabelecimentos que usam a plataforma — eles encontram seu app na vitrine pública e conectam pelo próprio painel, gerando credenciais reais.

Pré-requisitos antes de submeter

Como submeter

  1. Abra o app no painel.
  2. Na aba Visão geral, clique em Enviar para review.
  3. Se algum dos campos obrigatórios estiver vazio, o portal mostra exatamente o que falta antes de liberar o botão — preencha em Configurações e volte.
  4. O app entra no status “Aguardando review”. Você recebe email assim que a equipe avaliar.

O que muda após aprovação

Se for rejeitado

O motivo aparece no painel (aba Visão geral) e por email. Os ajustes mais comuns são: descrição vaga, logo de baixa qualidade, webhook não validando assinatura, ou implementação parcial dos endpoints obrigatórios. Corrija e clique em Enviar para review novamente — não há limite de tentativas.

Criar / acessar meus apps   Ver API completa (Full)