# Publicar primeira passagem

Ao final deste tutorial, você terá:

- Publicado uma passagem em sandbox via `POST /ec-api/v1/passagens`.
- Recebido o webhook `pe.passagem.reservada` (lock por canal autorizado).
- Recebido o webhook `pe.passagem.paga` com `nsu`, `txid`, `metodoPagamento`, `origemLiquidacao` e `reservaId`.
- Consultado o extrato e verificado o crédito na subconta.


**Tempo estimado:** 15 minutos.

## Pré-requisitos

- Credenciais de sandbox: `MV_EC_KEY` (Basic Auth) e `MV_EC_ID`.
- URL de webhook acessível publicamente (use [ngrok](https://ngrok.com) ou similar para desenvolvimento local).
- `curl` instalado.


## Passo 1 — Configurar variáveis de ambiente


```bash
export MV_EC_KEY="Basic <sua_credencial_base64>"
export MV_EC_ID="<seu_ec_id>"
export MV_BASE_URL="https://hml.api.pedagioeletronico.com.br"
```

Substitua os valores pelos fornecidos no onboarding de sandbox.

## Passo 2 — Verificar autenticação

Antes de publicar, confirme que as credenciais estão corretas:


```bash
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: $MV_EC_KEY" \
  -H "x-ec-id: $MV_EC_ID" \
  "$MV_BASE_URL/ec-api/v1/saldo"
```

Resposta esperada: `200`. Se receber `401`, verifique `MV_EC_KEY` e `MV_EC_ID`.

## Passo 3 — Publicar uma passagem


```bash
curl -s -X POST "$MV_BASE_URL/ec-api/v1/passagens" \
  -H "Authorization: $MV_EC_KEY" \
  -H "x-ec-id: $MV_EC_ID" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: TUTORIAL-001" \
  -d '{
    "referenciaExterna": "TUTORIAL-001",
    "placa": "ABC1D23",
    "valor": 12.50,
    "tipo": "FREEFLOW",
    "praca": { "id": "imigrantes-km245", "nome": "Imigrantes KM 245", "sentido": "SUL" },
    "categoria": 1,
    "capturadoEm": "2026-04-23T14:32:10Z"
  }'
```

Resposta esperada:


```json
{
  "success": true,
  "data": {
    "passagemId": "01JVZ8F4WH9R2T7K5M3N8P6Q4X",
    "referenciaExterna": "TUTORIAL-001",
    "status": "PENDENTE",
    "criadoEm": "2026-04-23T14:32:11Z"
  }
}
```

Guarde o `passagemId` para os próximos passos.

Idempotência
Se você repetir a chamada com a mesma `referenciaExterna` (e o mesmo `Idempotency-Key`), a API retorna o registro existente sem criar duplicata.

## Passo 4 — Configurar o endpoint de webhooks

Registre a URL onde você quer receber os eventos do ciclo de vida da passagem:


```bash
curl -s -X POST "$MV_BASE_URL/ec-api/v1/webhooks" \
  -H "Authorization: $MV_EC_KEY" \
  -H "x-ec-id: $MV_EC_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://sua-url.example.com/webhooks/movvia",
    "eventos": ["pe.passagem.reservada", "pe.passagem.paga", "pe.passagem.expirada", "pe.passagem.estornada", "pe.repasse.confirmado"]
  }'
```

A resposta inclui um `webhookSecret` — guarde-o; será usado para validar a assinatura HMAC dos eventos recebidos.

Ambiente de sandbox
Em sandbox, a Movvia simula automaticamente o fluxo completo após a publicação da passagem: emite `pe.passagem.reservada` em segundos e `pe.passagem.paga` em seguida. Você não precisa de um canal real para testar.

## Passo 5 — Receber `pe.passagem.reservada`

Em segundos após a publicação, seu endpoint recebe:


```http
POST {sua_url_webhook}
x-movvia-signature: <hmac-sha256-do-payload-bruto>
Content-Type: application/json

{
  "evento": "pe.passagem.reservada",
  "versao": "2",
  "id": "evt_8a3f1b2c9d4e",
  "timestamp": "2026-04-23T14:35:00Z",
  "ecId": "ec_sandbox_001",
  "dados": {
    "passagemId": "01JVZ8F4WH9R2T7K5M3N8P6Q4X",
    "referenciaExterna": "TUTORIAL-001",
    "reservaId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "valor": 12.50,
    "expiresAt": "2026-04-23T14:50:00Z",
    "canal": { "tipo": "PARCEIRO", "id": "sandbox-canal-simulado" }
  }
}
```

## Passo 6 — Receber `pe.passagem.paga`

Logo depois, o canal confirma o pagamento e você recebe o evento final:


```http
POST {sua_url_webhook}
x-movvia-signature: <hmac-sha256-do-payload-bruto>
Content-Type: application/json

{
  "evento": "pe.passagem.paga",
  "versao": "2",
  "id": "evt_3c4d5e6f7a8b",
  "timestamp": "2026-04-23T14:38:11Z",
  "ecId": "ec_sandbox_001",
  "dados": {
    "passagemId": "01JVZ8F4WH9R2T7K5M3N8P6Q4X",
    "referenciaExterna": "TUTORIAL-001",
    "reservaId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "valor": 12.50,
    "valorRepasseLiquido": 12.13,
    "comprovante": {
      "nsu": "000132547890",
      "txid": "MV20260423143810AB7F2C9D",
      "metodoPagamento": "PIX",
      "origemLiquidacao": "PARCEIRO",
      "canalId": "sandbox-canal-simulado",
      "pagoEm": "2026-04-23T14:38:10Z"
    }
  }
}
```

Valide a assinatura antes de processar:


```javascript
const crypto = require('crypto');

function validarWebhook(payloadBruto, assinaturaRecebida, segredoWebhook) {
  const assinaturaEsperada = crypto
    .createHmac('sha256', segredoWebhook)
    .update(payloadBruto, 'utf8')
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(assinaturaEsperada),
    Buffer.from(assinaturaRecebida)
  );
}

// No seu handler:
app.post('/webhooks/movvia', (req, res) => {
  const assinatura = req.headers['x-movvia-signature'];
  // IMPORTANTE: usar o body bruto, não JSON.stringify(req.body) — espaços diferentes invalidam a assinatura
  const valido = validarWebhook(req.rawBody, assinatura, process.env.MV_WEBHOOK_SECRET);
  if (!valido) return res.status(401).send('Assinatura inválida');
  res.status(200).send('OK');
  // processar evento de forma assíncrona
});
```


```python
import hashlib, hmac

def validar_webhook(payload_bruto: bytes, assinatura_recebida: str, segredo: str) -> bool:
    esperada = hmac.new(
        segredo.encode(), payload_bruto, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(esperada, assinatura_recebida)
```

Convenção idêntica à API Parceiros — ver [Webhooks Parceiros](/parceiros/guides/webhooks) para detalhes de retry, idempotência e timing.

## Passo 7 — Consultar o extrato

Após `pe.passagem.paga`, verifique o crédito no extrato da subconta:


```bash
curl -s "$MV_BASE_URL/ec-api/v1/extrato?desde=2026-04-23" \
  -H "Authorization: $MV_EC_KEY" \
  -H "x-ec-id: $MV_EC_ID"
```

Você deve ver uma linha de `CREDITO` com `referenciaExterna: "TUTORIAL-001"`, `valor: 12.13` (valorRepasseLiquido) e `metodoPagamento: "PIX"`.

## Próximo passo

- Leia o [ciclo de repasse](/estabelecimentos-comerciais/guides/ciclo-repasse) para entender quando esse crédito vira dinheiro na sua conta.
- Leia [disputas e estornos](/estabelecimentos-comerciais/guides/disputas-estornos) para preparar o fluxo de erros.
- Veja os casos de uso completos: [pedágio free flow](/estabelecimentos-comerciais/casos-de-uso/pedagio-freeflow) e [pedágio de barreira](/estabelecimentos-comerciais/casos-de-uso/pedagio-barreira).