OAuth (Ligar com o Vero)
Referência dos endpoints para plataformas que emitem em nome de outras empresas. Para o passo a passo, veja o guia.
Registar a app
Sem pedir ao Vero: no painel, em Chaves de API → Apps OAuth, ou pela API com a sessão do painel (não com chave de API nem token OAuth).
- Teste: qualquer empresa. Emite em sandbox, nada vai à AGT.
- Live: só empresas activas na AGT (com a chave AGT configurada). Emite facturas reais.
- Uma app live por verificar liga no máximo 100 empresas, e o ecrã de autorização avisa que não foi verificada. Para tirar o limite, peça a verificação ao suporte.
| GET | /v1/organisations/:orgId/oauth-apps | Apps da empresa, com empresas ligadas e limite |
| POST | /v1/organisations/:orgId/oauth-apps | Registar. Devolve o client_secret uma única vez |
| PATCH | /v1/organisations/:orgId/oauth-apps/:id | Editar nome, retornos, permissões ou webhook (o ambiente não muda) |
| DELETE | /v1/organisations/:orgId/oauth-apps/:id | Apagar. As empresas ligadas perdem o acesso na hora |
| POST | /v1/organisations/:orgId/oauth-apps/:id/rotate-secret | Novo client_secret (o anterior deixa de valer) |
| GET | /v1/organisations/:orgId/oauth-apps/:id/connections | Empresas ligadas |
| POST | /v1/organisations/:orgId/oauth-apps/:id/webhook/ping | Enviar um evento de teste |
| POST | /v1/organisations/:orgId/oauth-apps/:id/webhook/rotate-secret | Novo segredo de assinatura do webhook |
| GET | /v1/organisations/:orgId/oauth-apps/:id/webhook/events | Últimos eventos enviados |
| POST | /v1/organisations/:orgId/oauth-apps/:id/webhook/events/:eventId/redeliver | Reenviar um evento |
Corpo do POST
{
"name": "Loja Exemplo",
"environment": "test",
"redirectUris": ["https://loja.exemplo.ao/vero/callback"],
"allowedScopes": ["organisation:read", "invoices:write", "customers:write"],
"website": "https://loja.exemplo.ao",
"logoUrl": "https://loja.exemplo.ao/logo.png",
"contactEmail": "dev@loja.exemplo.ao",
"webhookUrl": "https://loja.exemplo.ao/vero/webhook"
}redirectUris: https (numa app Test também http://localhost ou 127.0.0.1; numa Live nunca) e sem #. webhookUrl: https público (nunca localhost; para testar use um túnel como o ngrok). Erros: 400 invalid_redirect_uri, 400 invalid_scope, 400 invalid_webhook_url, 403 org_not_activated (live sem chave AGT).
Autorização
Envie o comerciante para o ecrã do Vero (Authorization Code com PKCE S256, obrigatório):
https://vero.ao/oauth/authorize
?response_type=code
&client_id=app_test_...
&redirect_uri=https://loja.exemplo.ao/vero/callback
&scope=organisation:read invoices:write
&state=ALEATORIO
&code_challenge=BASE64URL(SHA256(code_verifier))
&code_challenge_method=S256Volta ao redirect_uri com code (vale 60 segundos, uma vez) e state, ou com error (ex.: access_denied).
Tokens
| POST | /oauth/token | grant_type=authorization_code (code, redirect_uri, code_verifier) ou refresh_token |
| POST | /oauth/revoke | Revogar um token (token=...). Desliga a empresa da app |
| GET | /v1/oauth/token-info | Empresa, permissões, app e ambiente do token em uso |
| GET | /.well-known/oauth-authorization-server | Metadados (RFC 8414) |
/oauth/token e /oauth/revoke aceitam application/x-www-form-urlencoded ou JSON, com o cliente em Basic auth ou em client_id/client_secret no corpo. Só a partir do servidor.
{
"access_token": "vero_oat_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "vero_ort_...",
"scope": "organisation:read invoices:write",
"organisation": { "id": "8f1c...", "name": "Padaria Kianda, Lda", "taxId": "5000123456" }
}O access token dura 1 hora. O refresh token muda a cada renovação e expira após 90 dias sem uso; se um refresh token já usado voltar a aparecer, a ligação é desligada (provável roubo).
O scope devolvido é o que a empresa aceitou, que pode ser menos do que o pedido: no ecrã de autorização o comerciante desmarca permissões, e depois pode retirar outras sem desligar a app. Uma permissão retirada vale de imediato (403 insufficient_scope com o mesmo token).
Com organisation:read, GET /v1/organisations/:id devolve só: id, name, legalName, taxId, email, phone, website, morada (addressLine1, addressLine2, city, province, country), logoUrl, eacCode, ivaRegime (geral | reduzido | simplificado | isento | null), defaultTaxRate, taxExemptionCode, status, agtKeysConfigured e partialPaymentsEnabled (sempre true: todas as empresas aceitam recibos parciais).
Webhooks da app
Um URL por app recebe os eventos de todas as empresas ligadas. Cada pedido traz X-Vero-Signature: sha256=HMAC_SHA256(segredo, corpo), X-Vero-Event e X-Vero-Delivery (id único, para ignorar repetidos). Sem resposta 2xx, o Vero tenta de novo após 1 min, 5 min, 30 min, 2 h e 8 h.
| invoice.agt_validated | Factura emitida pela app validada pela AGT |
| invoice.agt_rejected | Factura rejeitada pela AGT, com os erros |
| credit_note.agt_validated | Nota de crédito validada pela AGT |
| credit_note.agt_rejected | Nota de crédito rejeitada pela AGT |
| app.authorization.revoked | A empresa desligou a app (ou foi desligada) |
| ping | Evento de teste enviado do painel |