{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-estabelecimentos-comerciais/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["admonition"]},"type":"markdown"},"seo":{"title":"Publicar primeira passagem","description":"APIs públicas da Movvia para parceiros, estabelecimentos comerciais e clientes de dados veiculares.","meta":[{"name":"theme-color","content":"#7E3DEE"},{"name":"apple-mobile-web-app-title","content":"Movvia Docs"},{"name":"application-name","content":"Movvia Docs"}],"llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"publicar-primeira-passagem","__idx":0},"children":["Publicar primeira passagem"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Ao final deste tutorial, você terá:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Publicado uma passagem em sandbox via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /ec-api/v1/passagens"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Recebido o webhook ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pe.passagem.reservada"]}," (lock por canal autorizado)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Recebido o webhook ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pe.passagem.paga"]}," com ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["nsu"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["txid"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["metodoPagamento"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["origemLiquidacao"]}," e ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["reservaId"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Consultado o extrato e verificado o crédito na subconta."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Tempo estimado:"]}," 15 minutos."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"pré-requisitos","__idx":1},"children":["Pré-requisitos"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Credenciais de sandbox: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MV_EC_KEY"]}," (Basic Auth) e ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MV_EC_ID"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["URL de webhook acessível publicamente (use ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://ngrok.com"},"children":["ngrok"]}," ou similar para desenvolvimento local)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["curl"]}," instalado."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"passo-1--configurar-variáveis-de-ambiente","__idx":2},"children":["Passo 1 — Configurar variáveis de ambiente"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"export MV_EC_KEY=\"Basic <sua_credencial_base64>\"\nexport MV_EC_ID=\"<seu_ec_id>\"\nexport MV_BASE_URL=\"https://hml.api.pedagioeletronico.com.br\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Substitua os valores pelos fornecidos no onboarding de sandbox."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"passo-2--verificar-autenticação","__idx":3},"children":["Passo 2 — Verificar autenticação"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Antes de publicar, confirme que as credenciais estão corretas:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -s -o /dev/null -w \"%{http_code}\" \\\n  -H \"Authorization: $MV_EC_KEY\" \\\n  -H \"x-ec-id: $MV_EC_ID\" \\\n  \"$MV_BASE_URL/ec-api/v1/saldo\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Resposta esperada: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200"]},". Se receber ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["401"]},", verifique ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MV_EC_KEY"]}," e ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MV_EC_ID"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"passo-3--publicar-uma-passagem","__idx":4},"children":["Passo 3 — Publicar uma passagem"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -s -X POST \"$MV_BASE_URL/ec-api/v1/passagens\" \\\n  -H \"Authorization: $MV_EC_KEY\" \\\n  -H \"x-ec-id: $MV_EC_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: TUTORIAL-001\" \\\n  -d '{\n    \"referenciaExterna\": \"TUTORIAL-001\",\n    \"placa\": \"ABC1D23\",\n    \"valor\": 12.50,\n    \"tipo\": \"FREEFLOW\",\n    \"praca\": { \"id\": \"imigrantes-km245\", \"nome\": \"Imigrantes KM 245\", \"sentido\": \"SUL\" },\n    \"categoria\": 1,\n    \"capturadoEm\": \"2026-04-23T14:32:10Z\"\n  }'\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Resposta esperada:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"success\": true,\n  \"data\": {\n    \"passagemId\": \"01JVZ8F4WH9R2T7K5M3N8P6Q4X\",\n    \"referenciaExterna\": \"TUTORIAL-001\",\n    \"status\": \"PENDENTE\",\n    \"criadoEm\": \"2026-04-23T14:32:11Z\"\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Guarde o ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["passagemId"]}," para os próximos passos."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"success","name":"Idempotência"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Se você repetir a chamada com a mesma ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["referenciaExterna"]}," (e o mesmo ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key"]},"), a API retorna o registro existente sem criar duplicata."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"passo-4--configurar-o-endpoint-de-webhooks","__idx":5},"children":["Passo 4 — Configurar o endpoint de webhooks"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Registre a URL onde você quer receber os eventos do ciclo de vida da passagem:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -s -X POST \"$MV_BASE_URL/ec-api/v1/webhooks\" \\\n  -H \"Authorization: $MV_EC_KEY\" \\\n  -H \"x-ec-id: $MV_EC_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"url\": \"https://sua-url.example.com/webhooks/movvia\",\n    \"eventos\": [\"pe.passagem.reservada\", \"pe.passagem.paga\", \"pe.passagem.expirada\", \"pe.passagem.estornada\", \"pe.repasse.confirmado\"]\n  }'\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A resposta inclui um ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["webhookSecret"]}," — guarde-o; será usado para validar a assinatura HMAC dos eventos recebidos."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Ambiente de sandbox"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Em sandbox, a Movvia simula automaticamente o fluxo completo após a publicação da passagem: emite ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pe.passagem.reservada"]}," em segundos e ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pe.passagem.paga"]}," em seguida. Você não precisa de um canal real para testar."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"passo-5--receber-pepassagemreservada","__idx":6},"children":["Passo 5 — Receber ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pe.passagem.reservada"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Em segundos após a publicação, seu endpoint recebe:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"POST {sua_url_webhook}\nx-movvia-signature: <hmac-sha256-do-payload-bruto>\nContent-Type: application/json\n\n{\n  \"evento\": \"pe.passagem.reservada\",\n  \"versao\": \"2\",\n  \"id\": \"evt_8a3f1b2c9d4e\",\n  \"timestamp\": \"2026-04-23T14:35:00Z\",\n  \"ecId\": \"ec_sandbox_001\",\n  \"dados\": {\n    \"passagemId\": \"01JVZ8F4WH9R2T7K5M3N8P6Q4X\",\n    \"referenciaExterna\": \"TUTORIAL-001\",\n    \"reservaId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\",\n    \"valor\": 12.50,\n    \"expiresAt\": \"2026-04-23T14:50:00Z\",\n    \"canal\": { \"tipo\": \"PARCEIRO\", \"id\": \"sandbox-canal-simulado\" }\n  }\n}\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"passo-6--receber-pepassagempaga","__idx":7},"children":["Passo 6 — Receber ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pe.passagem.paga"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Logo depois, o canal confirma o pagamento e você recebe o evento final:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"POST {sua_url_webhook}\nx-movvia-signature: <hmac-sha256-do-payload-bruto>\nContent-Type: application/json\n\n{\n  \"evento\": \"pe.passagem.paga\",\n  \"versao\": \"2\",\n  \"id\": \"evt_3c4d5e6f7a8b\",\n  \"timestamp\": \"2026-04-23T14:38:11Z\",\n  \"ecId\": \"ec_sandbox_001\",\n  \"dados\": {\n    \"passagemId\": \"01JVZ8F4WH9R2T7K5M3N8P6Q4X\",\n    \"referenciaExterna\": \"TUTORIAL-001\",\n    \"reservaId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\",\n    \"valor\": 12.50,\n    \"valorRepasseLiquido\": 12.13,\n    \"comprovante\": {\n      \"nsu\": \"000132547890\",\n      \"txid\": \"MV20260423143810AB7F2C9D\",\n      \"metodoPagamento\": \"PIX\",\n      \"origemLiquidacao\": \"PARCEIRO\",\n      \"canalId\": \"sandbox-canal-simulado\",\n      \"pagoEm\": \"2026-04-23T14:38:10Z\"\n    }\n  }\n}\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Valide a assinatura antes de processar:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"const crypto = require('crypto');\n\nfunction validarWebhook(payloadBruto, assinaturaRecebida, segredoWebhook) {\n  const assinaturaEsperada = crypto\n    .createHmac('sha256', segredoWebhook)\n    .update(payloadBruto, 'utf8')\n    .digest('hex');\n  return crypto.timingSafeEqual(\n    Buffer.from(assinaturaEsperada),\n    Buffer.from(assinaturaRecebida)\n  );\n}\n\n// No seu handler:\napp.post('/webhooks/movvia', (req, res) => {\n  const assinatura = req.headers['x-movvia-signature'];\n  // IMPORTANTE: usar o body bruto, não JSON.stringify(req.body) — espaços diferentes invalidam a assinatura\n  const valido = validarWebhook(req.rawBody, assinatura, process.env.MV_WEBHOOK_SECRET);\n  if (!valido) return res.status(401).send('Assinatura inválida');\n  res.status(200).send('OK');\n  // processar evento de forma assíncrona\n});\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import hashlib, hmac\n\ndef validar_webhook(payload_bruto: bytes, assinatura_recebida: str, segredo: str) -> bool:\n    esperada = hmac.new(\n        segredo.encode(), payload_bruto, hashlib.sha256\n    ).hexdigest()\n    return hmac.compare_digest(esperada, assinatura_recebida)\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Convenção idêntica à API Parceiros — ver ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/parceiros/guides/webhooks"},"children":["Webhooks Parceiros"]}," para detalhes de retry, idempotência e timing."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"passo-7--consultar-o-extrato","__idx":8},"children":["Passo 7 — Consultar o extrato"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Após ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pe.passagem.paga"]},", verifique o crédito no extrato da subconta:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -s \"$MV_BASE_URL/ec-api/v1/extrato?desde=2026-04-23\" \\\n  -H \"Authorization: $MV_EC_KEY\" \\\n  -H \"x-ec-id: $MV_EC_ID\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Você deve ver uma linha de ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CREDITO"]}," com ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["referenciaExterna: \"TUTORIAL-001\""]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["valor: 12.13"]}," (valorRepasseLiquido) e ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["metodoPagamento: \"PIX\""]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"próximo-passo","__idx":9},"children":["Próximo passo"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Leia o ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/estabelecimentos-comerciais/guides/ciclo-repasse"},"children":["ciclo de repasse"]}," para entender quando esse crédito vira dinheiro na sua conta."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Leia ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/estabelecimentos-comerciais/guides/disputas-estornos"},"children":["disputas e estornos"]}," para preparar o fluxo de erros."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Veja os casos de uso completos: ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/estabelecimentos-comerciais/casos-de-uso/pedagio-freeflow"},"children":["pedágio free flow"]}," e ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/estabelecimentos-comerciais/casos-de-uso/pedagio-barreira"},"children":["pedágio de barreira"]},"."]}]}]},"headings":[{"value":"Publicar primeira passagem","id":"publicar-primeira-passagem","depth":1},{"value":"Pré-requisitos","id":"pré-requisitos","depth":2},{"value":"Passo 1 — Configurar variáveis de ambiente","id":"passo-1--configurar-variáveis-de-ambiente","depth":2},{"value":"Passo 2 — Verificar autenticação","id":"passo-2--verificar-autenticação","depth":2},{"value":"Passo 3 — Publicar uma passagem","id":"passo-3--publicar-uma-passagem","depth":2},{"value":"Passo 4 — Configurar o endpoint de webhooks","id":"passo-4--configurar-o-endpoint-de-webhooks","depth":2},{"value":"Passo 5 — Receber pe.passagem.reservada","id":"passo-5--receber-pepassagemreservada","depth":2},{"value":"Passo 6 — Receber pe.passagem.paga","id":"passo-6--receber-pepassagempaga","depth":2},{"value":"Passo 7 — Consultar o extrato","id":"passo-7--consultar-o-extrato","depth":2},{"value":"Próximo passo","id":"próximo-passo","depth":2}],"frontmatter":{"title":"Publicar primeira passagem — Tutorial Estabelecimentos Comerciais Movvia","description":"Passo a passo para publicar uma passagem em sandbox, simular reserva e pagamento, e validar os webhooks de retorno.","seo":{"title":"Publicar primeira passagem"}},"lastModified":"2026-04-25T18:15:22.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/estabelecimentos-comerciais/tutorials/publicar-primeira-cobranca","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}