Quickstart

Comece em 5 minutos

Obtenha uma API key, crie envios e rotas, gerencie a frota (veículos, motoristas, atribuições) e assine webhooks. A API Reference documenta as 41 rotas HTTP (68 operações) do contrato atual.

1

Obtenha sua API key

Peça ao administrador para acessar o dashboard, seção IntegraçõesAPI Keys, e gerar uma key com pelo menos:

  • shipments:read shipments:write shipments:delete
  • routes:read routes:write routes:delete routes:optimize
  • vehicles:read vehicles:write vehicles:delete
  • drivers:read drivers:write drivers:delete
  • assignments:read assignments:write assignments:delete
  • events:read webhooks:manage

A key é exibida UMA única vez ao criá-la. Guarde-a no seu gerenciador de segredos. Formato: mss_live_* para produção ou mss_test_* para sandbox.

Dica. Coloque os valores em variáveis para não misturar ambientes: export MASSIMPLE_BASE=https://api.massimple.la/v1, export MASSIMPLE_KEY=mss_test_xxx.
2

Valide que a key funciona

O endpoint /v1/whoami retorna a aplicação e a empresa à qual sua key aponta. Sanity check antes de qualquer outra coisa.

curl $MASSIMPLE_BASE/whoami \
  -H "Authorization: Bearer $MASSIMPLE_KEY"

Espera-se 200 com:

{
  "application": {
    "id": "...",
    "name": "Mi integración",
    "env": "test",
    "scopes": ["shipments:write", "events:read", "webhooks:manage"]
  },
  "company_id": "..."
}
3

Crie seu primeiro envio

POST em /v1/shipments. Não envie companyId nem status: o envio sempre é criado como pending. Se sua empresa tem um único centro de distribuição, pode omitir origin_distribution_center_id.

Campos principais (detalhe completo em API Reference → Criar envio):

CampoObrigatórioNotas
destinationSimObjeto destino
destination.addressSimEndereço; geocodificado se não houver lat/lng
destination.latitude / longitudeNão**Recomendados para roteirização
contentSimDescrição do envio
origin_distribution_center_idNãoObrigatório se tiver mais de um CD
external_id, amount_to_collect, scheduled_date, metadataNãoOpcionais
curl -X POST $MASSIMPLE_BASE/shipments \
  -H "Authorization: Bearer $MASSIMPLE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "external_id": "ORD-1001",
    "destination": {
      "name": "Juan Pérez",
      "address": "Av. Corrientes 1234, CABA",
      "latitude": -34.6037,
      "longitude": -58.3816,
      "contact_phone": "+5491155557777"
    },
    "content": "Caja con dos productos",
    "amount_to_collect": 0,
    "scheduled_date": "2026-05-20"
  }'

Resposta 201 com formato público: id (shp_...), tracking_number, external_id, etc.

4

Assine eventos com um webhook

Registre uma URL HTTPS pública no dashboard (Integrações → Webhooks) ou via API. Sempre que algo muda (status de envio, de rota, etc.) enviamos um POST assinado.

curl -X POST $MASSIMPLE_BASE/webhooks \
  -H "Authorization: Bearer $MASSIMPLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.tuempresa.com/webhooks/massimple",
    "events": ["*"]
  }'

Retornamos um secret que é exibido UMA vez. Guarde-o para verificar a assinatura HMAC de cada delivery.

Testar sem código. Use webhook.site: copie a URL única, registre-a aqui ou via API, escolha eventos * ou o preset rastreamento motorista (route.stop.status_changed, shipment.status_changed, route.optimized, route.status_changed). A lista de eventos não pode ser editada após criar o webhook.
Importante. Cada delivery vem com o header X-MasSimple-Signature: t=<unix-time>,v1=<hmac-hex>. A assinatura é calculada sobre <timestamp>.<body-raw> usando seu secret (whsec_…). Verifique a assinatura sempre, sobre o body RAW (não parseado).

Fluxo no seu servidor:

  1. Você recebe POST application/json na URL registrada.
  2. Lê o body como string exata (bytes como chegaram).
  3. Parseia t= e v1= do header X-MasSimple-Signature.
  4. Calcula HMAC-SHA256(secret, "<t>.<body-raw>") em hex.
  5. Compara com v1 (crypto.timingSafeEqual). Rejeite se faltar header, assinatura inválida ou timestamp > 5 min.
  6. Se válido → parseia JSON → processa → responde 200.
5

Verifique a assinatura de cada webhook

Função de verificação + handler Express de referência. Variável de ambiente WEBHOOK_SECRET=whsec_….

import crypto from 'crypto';

export function verifySignatureHeader({ header, body, secret, maxAgeSeconds = 300 }) {
  if (typeof header !== 'string') return false;
  const parts = Object.fromEntries(
    header.split(',').map((p) => {
      const idx = p.indexOf('=');
      return idx === -1 ? [p, ''] : [p.slice(0, idx).trim(), p.slice(idx + 1)];
    }),
  );
  const ts = Number(parts.t);
  if (!Number.isFinite(ts)) return false;
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - ts) > maxAgeSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${ts}.${body}`).digest('hex');
  const provided = parts.v1 || '';
  if (provided.length !== expected.length) return false;
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(provided));
}

Handler Express completo (use express.raw apenas nesta rota, não express.json() global):

import crypto from 'crypto';
import express from 'express';

const SECRET = process.env.WEBHOOK_SECRET;

function verifySignature(header, rawBody) {
  return verifySignatureHeader({ header, body: rawBody, secret: SECRET });
}

const app = express();

app.post(
  '/webhooks/massimple',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const rawBody = req.body.toString('utf8');
    const sig = req.headers['x-massimple-signature'];

    if (!verifySignature(sig, rawBody)) {
      return res.status(401).json({ error: 'invalid signature' });
    }

    const event = JSON.parse(rawBody);
    console.log('Evento verificado:', event.type, event.data);
    return res.status(200).json({ received: true });
  },
);

Debug manual: echo -n "$TS.$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex — o hex deve coincidir com v1= do header.

Se o verificador retornar true, já pode parsear o body e processar o evento. Retorne 200 no final — qualquer 5xx dispara nosso retry exponencial (1m, 5m, 30m, 2h, 12h, 24h).

5b

Auditoria: consulte o stream com GET /events

Complemento aos webhooks: pull em vez de push. Scope events:read. Lista o que o +simple emitiu (30 dias) mesmo que falhe o POST ao webhook ou você não tenha URL pública. Mesmo formato CloudEvents de um delivery (id, type, time, data).

QueryUso
limitMáx por página (default 50, máx 200)
typesFiltro separado por vírgula, ex. shipment.status_changed,route.stop.status_changed
afterCursor next_cursor da página anterior
curl "$MASSIMPLE_BASE/events?limit=50&types=route.stop.status_changed,shipment.status_changed" \
  -H "Authorization: Bearer $MASSIMPLE_KEY"
{
  "events": [
    {
      "id": "evt_6a16103d86b6e449bc2ffc76",
      "type": "shipment.status_changed",
      "source": "masSimple",
      "specversion": "1.0",
      "time": "2026-05-26T21:27:25.432Z",
      "data": { "external_id": "ORD-1001", "current_status": "delivered" }
    }
  ],
  "next_cursor": "6a16103d86b6e449bc2ffc77"
}

Se next_cursor não for null, peça a próxima página com ?after=next_cursor. No demo Bazarshop (aba Eventos) você pode testar o polling com filtros sem curl. Detalhe em API Reference → Polling de eventos e Dashboard → Integrações → Endpoints.

6

Opere por external_id

Se você guarda seu próprio ID de pedido (ORD-1001), não precisa do shp_... do Massimple:

# Consultar
curl $MASSIMPLE_BASE/shipments/external/ORD-1001 \
  -H "Authorization: Bearer $MASSIMPLE_KEY"

# Actualizar (solo content y monto a cobrar)
curl -X PATCH $MASSIMPLE_BASE/shipments/external/ORD-1001 \\
  -H "Authorization: Bearer $MASSIMPLE_KEY" \\
  -H "Content-Type: application/json" \\
  -d '{"content": "Caja actualizada", "amount_to_collect": 1500}'

Listagem, atualização e exclusão por external_id usam as mesmas rotas com prefixo /shipments/external/.... O PATCH só aceita content e amount_to_collect (não destino, datas nem metadata). Para cancelar: POST /v1/shipments/{id}/cancel (ou .../external/{external_id}/cancel) com body opcional {"error_comment":"..."} — não é necessário enviar status. Emite shipment.status_changed e shipment.cancelled. O status failed não está na API pública: só o motorista marca pela DriverApp. Para recriar um envio cancelled ou failed: POST /v1/shipments/{id}/recreate (ou .../external/{external_id}/recreate) com {"scheduled_date":"YYYY-MM-DD"} — cria um envio novo em pending com os mesmos dados; o original não muda. Emite shipment.created. Para alta em massa: POST /v1/shipments/bulk e POST /v1/shipments/bulk-delete (ambos aceitam Idempotency-Key).

# Cancelar envío (por id o external_id)
curl -X POST $MASSIMPLE_BASE/shipments/external/ORD-1001/cancel \\
  -H "Authorization: Bearer $MASSIMPLE_KEY" \\
  -H "Content-Type: application/json" \\
  -d '{"error_comment": "Cliente solicitó anulación"}'

# Recrear envío cancelado/fallido (nuevo pending, misma data)
curl -X POST $MASSIMPLE_BASE/shipments/external/ORD-1001/recreate \\
  -H "Authorization: Bearer $MASSIMPLE_KEY" \\
  -H "Content-Type: application/json" \\
  -d '{"scheduled_date": "2026-06-10"}'

Envios sem rota atribuída: GET /v1/shipments/available (pending, sem assigned_route_id).

6b

Consulte o que está livre para atribuir

Não há endpoints separados /unassigned. Use filtros nos listados:

RecursoRequestO que retorna
Envios para rotearGET /shipments/availablePendentes sem rota
Após excluir uma rotaGET /shipments/availableOs envios da rota excluída voltam a pending
Rotas sem motorista/veículoGET /routes?has_assignment=falseSem atribuição ativa
Rotas prontas para atribuirGET /routes?has_assignment=false&status=optimizedOtimizadas, sem frota
Motoristas livresGET /drivers?available_only=trueAtivos, sem veículo atribuído
Veículos livresGET /vehicles?available_only=trueDisponíveis, sem motorista
# Rutas sin asignación (ejemplo)
curl "$MASSIMPLE_BASE/routes?has_assignment=false&status=optimized&limit=50" \
  -H "Authorization: Bearer $MASSIMPLE_KEY"

Detalhe de cada query param em API Reference → Listar rotas.

7

Crie e otimize uma rota

Com scopes routes:write e routes:optimize. Você pode montar uma rota a partir de envios pendentes:

curl -X POST $MASSIMPLE_BASE/routes \
  -H "Authorization: Bearer $MASSIMPLE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "external_id": "ROUTE-2026-001",
    "name": "Ruta mañana CABA",
    "shipment_external_ids": ["ORD-1001", "ORD-1002"],
    "optimize": true
  }'

Resposta 201 com id (rte_...), status (ex. optimized) e stops_count. Para adicionar ou remover envios em uma rota existente: PATCH /v1/routes/:id com add_shipment_ids / remove_shipment_ids (apenas draft/optimized sem assignment). Para reordenar paradas: PATCH /v1/routes/:id/stops/sequence. Para otimizar depois: POST /v1/routes/:id/optimize.

Excluir rota. Scope routes:delete. Apenas draft ou optimized sem assignment ativo. Se a rota está atribuída → primeiro POST /v1/routes/:id/unassign, depois DELETE. Resposta 204 sem body.
Envios após o DELETE. Todos os envios dessa rota passam sempre a pending com assigned_route_id null (mesmo que na rota estivessem em assigned). Veja-os novamente com GET /v1/shipments/available e monte outra rota com POST /v1/routes.
# Borrar ruta (sin assignment)
curl -i -X DELETE "$MASSIMPLE_BASE/routes/rte_507f1f77bcf86cd799439011" \
  -H "Authorization: Bearer $MASSIMPLE_KEY"

# Por external_id del ERP
curl -i -X DELETE "$MASSIMPLE_BASE/routes/external/ROUTE-2026-001" \
  -H "Authorization: Bearer $MASSIMPLE_KEY"

Com assignment na rota, DELETE responde 409. Detalhe em API Reference → Rotas: excluir, desatribuir e liberar frota.

8

Frota: veículo e motorista

Scopes vehicles:* e drivers:*. Mesmos padrões que envios: CRUD, external_id e bulk.

# Vehículo
curl -X POST $MASSIMPLE_BASE/vehicles \
  -H "Authorization: Bearer $MASSIMPLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "VEH-01",
    "license_plate": "AB123CD",
    "distribution_center_id": "dc_507f1f77bcf86cd799439012"
  }'

# Conductor (sin password → temporary_password en la respuesta)
curl -X POST $MASSIMPLE_BASE/drivers \
  -H "Authorization: Bearer $MASSIMPLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "DRV-01",
    "document": "30111222",
    "phone": "+5491112345678",
    "license_number": "LIC-123",
    "license_type": "B"
  }'

A resposta de criar motorista pode incluir temporary_password uma única vez. Guarde-a para o primeiro login na DriverApp. Se você enviar password, não é retornada.

Excluir frota. DELETE /v1/vehicles/:id e DELETE /v1/drivers/:id respondem 204 sem body se o recurso estiver livre (sem atribuição ativa). No seu cliente não parseie JSON: use res.ok ou status 204. Se houver assignment → 409.
8b

Editar recursos (PATCH)

Cada PATCH só aceita campos específicos; outros campos respondem 400. Detalhe em API Reference → Edição parcial.

RecursoCamposNotas
Envio (PATCH)content, amount_to_collectNão se delivered/cancelled/failed
Envio (cancelar)POST .../cancelerror_comment opcionalcancelled; failed é só DriverApp
Envio (recriar)POST .../recreatescheduled_date obrigatórioSó se origem cancelled/failed; novo pending; emite shipment.created
VeículomileageObrigatório
Motoristaphone, license_number, preferred_areas, experienceNão nome nem documento
Rotaadd_shipment_*, remove_shipment_*Sem assignment; envios de /shipments/available
# Vehículo — solo kilometraje
curl -X PATCH $MASSIMPLE_BASE/vehicles/external/VEH-01 \\
  -H "Authorization: Bearer $MASSIMPLE_KEY" \\
  -H "Content-Type: application/json" \\
  -d '{"mileage": 45200}'

# Ruta — agregar/quitar envíos
curl -X PATCH $MASSIMPLE_BASE/routes/external/ROUTE-2026-001 \\
  -H "Authorization: Bearer $MASSIMPLE_KEY" \\
  -H "Content-Type: application/json" \\
  -d '{"add_shipment_external_ids":["ORD-1003"],"remove_shipment_external_ids":["ORD-1001"]}'
9

Atribua motorista e veículo a uma rota

Scope assignments:write. Motorista e veículo devem estar no mesmo centro de distribuição e disponíveis.

curl -X POST $MASSIMPLE_BASE/assignments \
  -H "Authorization: Bearer $MASSIMPLE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "external_id": "ASG-001",
    "driver_external_id": "DRV-01",
    "vehicle_external_id": "VEH-01",
    "route_external_id": "ROUTE-2026-001"
  }'

Antes de atribuir, você pode listar recursos livres (passo 6b). Para liberar frota: se a atribuição tem rota, primeiro POST /v1/routes/:id/unassign (rota assigned, sem ter iniciado). Isso desvincula a rota e deixa motorista+veículo na atribuição (active). Depois POST /v1/assignments/:id/unassign só se route_id for null — com rota vinculada o endpoint de atribuição responde 409. A rota otimizada volta a listar com GET /routes?has_assignment=false.

10

Execução em rota (app motorista)

Após POST /assignments, o motorista opera a rota pela DriverApp (sliders). Isso não usa endpoints públicos /v1: é API corp / SDUI interna (PUT /api/corp/routes/:id/change-status-to-stop, ação deliver_current_stop).

Re-otimização automática. Sempre que o motorista termina uma parada (entrega, falha ou skip), o backend reordena apenas as paradas pending conforme o tráfego (Google Routes). Se a ordem mudar, você recebe route.optimized no seu webhook.

Webhooks de rastreamento. Cada slider do motorista emite route.stop.status_changed e shipment.status_changed. Ao completar a última parada, route.status_changed para completed. O payload da parada inclui tracking_number, external_id, stop_type, sequence, previous_status e current_status.

Ação motoristaEventos webhook típicos
Começar / a caminhoroute.stop.status_changed, shipment.status_changed (in_transit)
Entrega concluídaIgual + possível route.optimized
Última parada+ route.status_changed → completed

Auditoria: GET /v1/events?types=route.stop.status_changed,shipment.status_changed (events:read) lista o emitido mesmo que falhe o POST ao webhook.

Consulte a sequência atualizada com GET /v1/routes/:id/stops. Detalhe em API Reference → Execução em rota e Dashboard → Integrações → Endpoints.

Pronto. Com os passos anteriores você tem o fluxo de integração: envios → rota → frota → atribuição → execução na app motorista (webhooks). A API pública não expõe mudança de status de paradas em rota. A API Reference documenta PATCH restritivo por recurso e re-otimização automática de pendentes. Após mudanças no backend: cd backend && npm run sync-openapi-v2 antes de publicar a web.