Let's Go Delivery — API Open Delivery (Logística)
Versão do padrão: Open Delivery v1.7.1 · Papel do Let's Go: operador logístico (Logistics Service)
1. Introdução
O Let's Go Delivery recebe pedidos de entrega do seu sistema pelo padrão Open Delivery, despacha um entregador (moto) e devolve o andamento por webhook.
Fluxo resumido:
- Seu sistema obtém um token (
/oauth/token) com as credenciais da loja. - Seu sistema cria a entrega (
/v1/logistics/delivery). - O Let's Go despacha o entregador e envia cada mudança de status para o seu webhook.
- Seu sistema pode consultar os detalhes a qualquer momento e cancelar antes da coleta.
- A cobrança das entregas é semanal, por fatura.
Nesta documentação, <client_id>, <client_secret>, <access_token> e <seu MerchantId> são marcadores: troque pelos valores reais.
2. Trocar informações com o estabelecimento
2.1 O que o Let's Go entrega a você
| Informação | Como é entregue |
|---|---|
| URL base e AppId do Let's Go | nesta documentação (seção 3) |
client_id da loja | por um canal (ex.: e-mail) |
client_secret da loja | por outro canal, separado (ex.: gerenciador de senhas com link de uso único) |
- As credenciais são criadas pelo Let's Go, uma por loja, no nosso painel de administração.
- O
client_secreté exibido uma única vez no momento da criação; não temos como reenviá-lo. Se for perdido, revogamos a credencial e emitimos outra.
2.2 O que você entrega ao Let's Go
| Informação | Uso |
|---|---|
URL do seu webhook (https://…) | onde enviamos os eventos de status |
| Seu MerchantId (identificador da loja no seu sistema) | vai no cabeçalho X-App-MerchantId e em merchant.id |
| Seu AppId | identificação do seu sistema |
| Contato técnico | suporte durante a integração |
2.3 Modo teste e liberação para produção
- Toda credencial nasce em modo teste. Nesse modo, as entregas criadas:
- são aceitas normalmente (
202, eventoACCEPTED); - não são despachadas a entregadores e não são cobradas;
- geram webhook normalmente para a URL cadastrada;
- podem ter o status avançado manualmente pela nossa equipe, para você validar os demais eventos;
- são canceladas pela nossa equipe ao fim dos testes (evento
CANCELLED).
- são aceitas normalmente (
- Depois que os testes forem validados, o Let's Go libera a credencial para produção. A partir daí as entregas são despachadas de verdade e cobradas.
3. URL base e APP-ID
| Item | Valor |
|---|---|
| URL base | https://astbkmpegcmqljltmdpx.supabase.co/functions/v1/open-delivery |
| APP-ID do Let's Go | 070b173c-70f5-4dc6-910f-6f962ec419e4 |
A mesma URL atende teste e produção; o que muda é a credencial.
4. Autenticação
**POST {URL_BASE}/oauth/token**
Content-Type: application/x-www-form-urlencoded- Campos:
grant_type=client_credentials,client_id,client_secret
Requisição:
POST /functions/v1/open-delivery/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=<client_id>&client_secret=<client_secret>Resposta 200:
{ "access_token": "<access_token>", "token_type": "bearer", "expires_in": 3600 }Regras:
- Envie
Authorization: Bearer <access_token>em todas as demais chamadas. - O token vale 3600 segundos (1 hora). Guarde e reaproveite; não peça um token por chamada.
- Não há refresh token: quando expirar, chame
/oauth/tokende novo. - A loja é identificada pelo token; não existe campo de loja no corpo dos pedidos.
- Limite: 20 tokens por minuto por credencial.
- Proteção: 10 tentativas com falha em 10 minutos bloqueiam, por 10 minutos, o IP e o
client_idenvolvidos (429). - Credencial revogada: o token deixa de valer na hora.
Erros: 401 invalid_request (corpo não é form-urlencoded), 401 unsupported_grant_type, 401 invalid_client, 429 too_many_requests, 503 service_unavailable.
5. Criar entrega
**POST {URL_BASE}/v1/logistics/delivery**
Authorization: Bearer <access_token>Content-Type: application/json(corpo de até 64 KB)
5.1 Campos lidos pelo Let's Go
| Campo | Obrigatório | Observação |
|---|---|---|
orderId | sim | identificador do pedido no seu sistema; chave de idempotência por loja (até 100 caracteres) |
orderDisplayId | sim | número exibido ao entregador e no painel (até 50 caracteres) |
customerName | sim | nome do cliente |
customerPhone | não | telefone do cliente |
deliveryAddress | sim | ver Address; **latitude e longitude obrigatórias** |
vehicle | sim | ver Vehicle; type deve conter MOTORBIKE_BAG ou MOTORBIKE_BOX |
returnToMerchant | não | precisa ser false (ou ausente) |
payments.method | não | ONLINE (padrão). OFFLINE não é aceito |
specialInstructions | não | instruções para o entregador (até 500 caracteres) |
Outros campos da especificação podem ser enviados, mas são ignorados nesta versão. O endereço de coleta é sempre o endereço da loja cadastrado no Let's Go.
5.2 Exemplo de requisição
{
"orderId": "PEDIDO-12345",
"orderDisplayId": "1234",
"customerName": "Maria Silva",
"customerPhone": "16999990000",
"deliveryAddress": {
"country": "BR",
"state": "SP",
"city": "Ribeirão Preto",
"district": "Centro",
"street": "Rua Exemplo",
"number": "100",
"postalCode": "14000000",
"complement": "Ap 12",
"latitude": -21.1775,
"longitude": -47.8103
},
"vehicle": { "type": ["MOTORBIKE_BAG"], "container": "NORMAL" },
"returnToMerchant": false,
"payments": { "method": "ONLINE" },
"specialInstructions": "Interfone 12"
}5.3 Resposta 202
{
"deliveryId": "3f1c2a9e-0000-4000-8000-000000000000",
"event": "ACCEPTED",
"completion": { "estimate": "2026-10-06T18:40:00Z" }
}completion.estimate: previsão de conclusão (UTC), calculada pela distância.
5.4 Regras e limites
- Coordenada obrigatória:
deliveryAddress.latitude/longitudeválidas, dentro do Brasil e diferentes de0,0. Caso contrário: **422 invalid_delivery_coordinates**. - Distância máxima: 32 km entre a loja e o cliente, pela rota. Acima disso:
422 distance_out_of_range. - Limite diário: 300 entregas por dia por loja (dia de Brasília). Acima disso:
429 daily_limit_reached. - Idempotência: reenviar o mesmo
orderId(inclusive em chamadas simultâneas) devolve a mesma entrega e o mesmodeliveryId, sem duplicar. - Taxa: calculada na criação e congelada (ver seção 8.1 e DeliveryPrice).
- Se o cálculo de rota estiver momentaneamente indisponível:
503 route_unavailable(pode repetir a chamada com o mesmoorderId).
6. Cancelar entrega
**POST {URL_BASE}/v1/logistics/cancel/{orderId}**
Authorization: Bearer <access_token>Content-Type: application/json(corpo de até 8 KB)
Requisição:
{
"reason": "CONSUMER_CANCELLATION_REQUESTED",
"action": "CANCEL_DELIVERY",
"message": "Cliente desistiu"
}| Campo | Obrigatório | Valores |
|---|---|---|
reason | sim | CONSUMER_CANCELLATION_REQUESTED, NO_SHOW, PROBLEM_AT_MERCHANT, HIGH_ACCEPTANCE_TIME, INCORRECT_ORDER_OR_PRODUCT_PICKUP, PROBLEM_RESOLUTION, DISCOMBINE_ORDER, OTHER |
action | não | registrado; não altera o comportamento nesta versão |
message | não | registrado (até 300 caracteres) |
Resposta 202:
{ "additionalCharges": false }Regras:
| Situação da entrega | Resultado |
|---|---|
| Ainda não coletada (aceita, entregador a caminho ou na loja) | cancela; 202, sem taxa |
| Já cancelada | 202 (idempotente) |
| Já coletada / a caminho do cliente | 422 cannot_cancel_after_pickup |
| Concluída | 422 already_delivered |
reason fora da lista | 400 invalid_reason |
| Entrega inexistente ou de outra loja | 404 not_found |
Após o cancelamento, o evento CANCELLED é enviado ao seu webhook.
7. Detalhes da entrega
**GET {URL_BASE}/v1/logistics/delivery/{orderId}**
Authorization: Bearer <access_token>
Resposta 200:
{
"deliveryId": "3f1c2a9e-0000-4000-8000-000000000000",
"orderId": "PEDIDO-12345",
"orderDisplayId": "1234",
"merchant": { "id": "<seu MerchantId>", "name": "Nome da Loja" },
"customerName": "Maria Silva",
"customerPhone": "16999990000",
"events": [
{ "type": "ACCEPTED", "datetime": "2026-10-06T18:00:00Z" },
{ "type": "PICKUP_ONGOING", "datetime": "2026-10-06T18:03:00Z" },
{ "type": "ARRIVED_AT_MERCHANT", "datetime": "2026-10-06T18:12:00Z" }
],
"vehicle": { "type": ["MOTORBIKE_BAG"], "container": "NORMAL" },
"deliveryPrice": {
"price": { "value": 15.10, "currency": "BRL" },
"pricingList": "DYNAMIC",
"additionalPricePercentual": 24.79
},
"deliveryPerson": { "id": "…", "name": "Nome do Entregador", "phone": "16…" }
}events: eventos já ocorridos, em ordem cronológica, horários em UTC (ISO 8601).merchant.id: o seu MerchantId (se informado); caso contrário, o identificador da loja no Let's Go.deliveryPerson: só aparece depois que um entregador é alocado.- Entrega inexistente ou de outra loja:
404 not_found(não revelamos se ela existe).
8. Webhook de status
8.1 Envio
Enviamos um POST para a URL que você cadastrou a cada mudança de status.
Requisitos da URL: **https, porta 443, domínio público** (não aceitamos IP, localhost nem nomes internos).
8.2 Headers
Content-Type: application/json
User-Agent: LetsGo-OpenDelivery/1.0
X-App-Id: 070b173c-70f5-4dc6-910f-6f962ec419e4
X-App-MerchantId: <seu MerchantId>
X-App-Signature: <assinatura HMAC>8.3 Corpo
{
"deliveryId": "3f1c2a9e-0000-4000-8000-000000000000",
"orderId": "PEDIDO-12345",
"orderDisplayId": "1234",
"merchant": { "id": "<seu MerchantId>", "name": "Nome da Loja" },
"event": { "type": "PICKUP_ONGOING", "datetime": "2026-10-06T18:03:00Z" },
"customerName": "Maria Silva",
"vehicle": { "type": ["MOTORBIKE_BAG"], "container": "NORMAL" },
"deliveryPrice": {
"price": { "value": 15.10, "currency": "BRL" },
"pricingList": "DYNAMIC",
"additionalPricePercentual": 24.79
},
"deliveryPerson": { "id": "…", "name": "Nome do Entregador", "phone": "16…" }
}deliveryPerson só aparece quando há entregador alocado.
8.4 Assinatura HMAC
X-App-Signature= HMAC-SHA256 do corpo bruto da requisição (os bytes exatamente como recebidos), em hexadecimal minúsculo, usando o **client_secretda loja** como chave.- Valide antes de interpretar o JSON e compare em tempo constante.
Exemplo em Node.js (Express):
const crypto = require('crypto');
const express = require('express');
const app = express();
// guarde o corpo BRUTO; não valide sobre o JSON re-serializado
app.post('/deliveryEvent', express.raw({ type: 'application/json' }), (req, res) => {
const recebida = String(req.get('X-App-Signature') || '');
const esperada = crypto
.createHmac('sha256', process.env.LETSGO_CLIENT_SECRET)
.update(req.body) // Buffer com o corpo bruto
.digest('hex'); // hexadecimal minúsculo
const ok = recebida.length === esperada.length &&
crypto.timingSafeEqual(Buffer.from(recebida), Buffer.from(esperada));
if (!ok) return res.status(401).end();
const evento = JSON.parse(req.body.toString('utf8'));
// trate a repetição por evento.deliveryId + evento.event.type
res.status(204).end();
});8.5 Resposta, novas tentativas e ordem
- Responda com qualquer 2xx (ex.:
200ou204) em até 5 segundos. O corpo da resposta é ignorado. - Redirecionamentos (
3xx) não são seguidos e contam como falha. - Sem
2xx, reenviamos após 1, 2, 5, 10, 20, 30, 60, 60, 120, 180, 240 e 360 minutos (12 tentativas, ~18 horas). Depois disso o evento é descartado, e nossa equipe pode reenviá-lo manualmente. - Ordem garantida por entrega: o próximo evento de uma entrega só é enviado depois que o anterior foi aceito (ou descartado).
- Cada tipo de evento é gerado uma única vez por entrega, mas a entrega do webhook é "pelo menos uma vez": trate a repetição por
deliveryId+event.type.
8.6 Eventos
event.type | Quando |
|---|---|
ACCEPTED | entrega criada e aceita pelo Let's Go |
PICKUP_ONGOING | entregador alocado, a caminho da loja |
ARRIVED_AT_MERCHANT | entregador chegou à loja |
ORDER_PICKED | pedido coletado |
DELIVERY_ONGOING | a caminho do cliente |
ARRIVED_AT_CUSTOMER | entregador chegou ao cliente |
ORDER_DELIVERED | pedido entregue |
DELIVERY_FINISHED | entrega encerrada |
CANCELLED | entrega cancelada (pela API, pelo cliente ou pela nossa operação) |
Depois de DELIVERY_FINISHED ou CANCELLED, nenhum outro evento é enviado.
8.7 Taxa de entrega
- A taxa segue a tabela de preço por distância da sua loja, pela distância de rota loja → cliente.
- Em períodos de alta demanda, nossa operação pode ativar um acréscimo de preço dinâmico por cidade, válido por até 2 horas. Por isso a taxa pode variar conforme o horário e a cidade.
- O valor vigente no momento da criação fica congelado para aquela entrega e é o que vai para a fatura.
- O valor aparece em
deliveryPrice, nos Detalhes da entrega e em todos os webhooks.
9. Schemas auxiliares
9.1 Address
| Campo | Tipo | Obrigatório | Observação |
|---|---|---|---|
street | string | recomendado | usado no endereço exibido ao entregador |
number | string | recomendado | |
complement | string | não | |
district | string | recomendado | |
city | string | recomendado | |
state | string | recomendado | |
country, postalCode, reference | string | não | aceitos e ignorados |
latitude | number | sim | dentro do Brasil, diferente de 0 |
longitude | number | sim | dentro do Brasil, diferente de 0 |
9.2 Vehicle
| Campo | Tipo | Observação |
|---|---|---|
type | array de string | deve conter MOTORBIKE_BAG ou MOTORBIKE_BOX; outros (CAR, BICYCLE, SCOOTER, VUC) são recusados |
container | string | NORMAL ou THERMIC (informativo) |
Nas respostas e webhooks, o Let's Go informa { "type": ["MOTORBIKE_BAG"], "container": "NORMAL" }.
9.3 Event
| Campo | Tipo | Observação |
|---|---|---|
type | string | ver tabela da seção 8.6 |
datetime | string (ISO 8601, UTC) | momento do evento |
9.4 DeliveryPrice
| Campo | Tipo | Observação |
|---|---|---|
price.value | number | valor congelado da entrega |
price.currency | string | sempre BRL |
pricingList | string | NORMAL (só tabela) ou DYNAMIC (com acréscimo dinâmico) |
additionalPricePercentual | number | percentual do acréscimo dinâmico sobre o valor de tabela; 0 se não houver |
9.5 Merchant
| Campo | Tipo | Observação |
|---|---|---|
id | string | seu MerchantId (ou o identificador da loja no Let's Go) |
name | string | nome da loja no Let's Go |
9.6 DeliveryPerson
| Campo | Tipo | Observação |
|---|---|---|
id | string | identificador do entregador |
name | string | nome |
phone | string | telefone |
9.7 Error
{ "title": "<código>", "status": 422 }10. Códigos de erro
| HTTP | title | Rota | Quando |
|---|---|---|---|
| 400 | missing_required_fields | criar entrega | falta orderId, orderDisplayId, customerName, deliveryAddress ou vehicle |
| 400 | invalid_json | criar / cancelar | corpo não é JSON válido |
| 400 | invalid_content_type | criar entrega | Content-Type diferente de application/json |
| 400 | payload_too_large | criar / cancelar | corpo acima de 64 KB (criar) ou 8 KB (cancelar) |
| 400 | invalid_reason | cancelar | reason fora da lista |
| 401 | invalid_request | /oauth/token | corpo não é form-urlencoded |
| 401 | unsupported_grant_type | /oauth/token | grant_type diferente de client_credentials |
| 401 | invalid_client | /oauth/token | client_id/client_secret inválidos ou credencial revogada |
| 401 | unauthorized | demais rotas | token ausente, inválido, expirado ou revogado |
| 403 | merchant_inactive | criar entrega | loja inativa no Let's Go |
| 404 | not_found | detalhes / cancelar / rota desconhecida | entrega inexistente ou de outra loja |
| 422 | invalid_delivery_coordinates | criar entrega | coordenada de entrega ausente, inválida, fora do Brasil ou 0,0 |
| 422 | vehicle_not_supported | criar entrega | vehicle.type sem MOTORBIKE_BAG/MOTORBIKE_BOX |
| 422 | return_to_merchant_not_supported | criar entrega | returnToMerchant: true |
| 422 | offline_payment_not_supported | criar entrega | payments.method: OFFLINE |
| 422 | distance_out_of_range | criar entrega | mais de 32 km |
| 422 | merchant_not_configured | criar entrega | loja sem endereço/coordenada cadastrados |
| 422 | merchant_billing_not_supported | criar entrega | loja fora da modalidade de faturamento |
| 422 | cannot_cancel_after_pickup | cancelar | pedido já coletado |
| 422 | already_delivered | cancelar | entrega concluída |
| 429 | too_many_requests | /oauth/token | limite de tokens ou bloqueio por tentativas com falha |
| 429 | daily_limit_reached | criar entrega | limite de 300 entregas por dia |
| 503 | route_unavailable | criar entrega | cálculo de rota indisponível; repita com o mesmo orderId |
| 503 | service_unavailable | todas | falha temporária |
11. Fora desta versão
Não estão disponíveis nesta versão:
- Avisos de problema (
handleProblem). - Webhook de código de confirmação (
/confirmationCode). - Polling de eventos (
/events:pollinge/events/acknowledgment). - Retorno à loja (
returnToMerchant) e pagamento na entrega com troco (OFFLINE). - Veículos que não sejam moto.
- Entregas combinadas (
combinedOrdersIds). - Página de rastreio (
externalTrackingURL).
12. Segurança e suporte
- Guarde o
client_secretem cofre ou variável de ambiente, nunca em código, planilha ou mensagem. - Em caso de vazamento, avise imediatamente: revogamos a credencial (os tokens param na hora) e emitimos outra.
- Contato técnico: letsgodelivery@letsgodelivery.com.br · (11) 99170-2772