Referência da API

Autenticação, endpoints, payloads de webhooks e verificação de assinaturas para a API do Salonnare.

Referência da API

Ainda não disponível

Esta funcionalidade ainda está marcada como Brevemente na app: não podes criar chaves de API nem webhooks neste momento. Se abrires Definições > API e Webhooks, só vês um aviso de que a funcionalidade está a caminho. Esta página descreve como vai funcionar quando a funcionalidade estiver disponível.

Esta página é a referência técnica para programadores que constroem uma integração com a API do Salonnare. Pressupõe que já sabes o que é uma chave de API e um webhook, se procuras a explicação em linguagem simples e os ecrãs para os criar, consulta API e Webhooks.

Todas as afirmações abaixo são tiradas diretamente do código-fonte do servidor Salonnare. Caso o código e esta página alguma vez discordem, confia numa nova verificação do código, este documento pode ficar desatualizado, o código é o que realmente corre.

Autenticação

Envia a tua chave de API em cada pedido no cabeçalho X-API-Key:

curl https://<your-salon>.salonnare.com/api/bookings \
  -H "X-API-Key: sk_live_a1b2_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Formato da chave

Uma chave tem o aspeto sk_live_<4 carateres hex>_<48 carateres hex>, por exemplo:

sk_live_a1b2_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Apenas os primeiros 12 carateres (sk_live_ mais o segmento de 4 carateres) são guardados ou voltam a ser mostrados após a criação. Os restantes 48 carateres são o segredo em si. O Salonnare guarda um hash SHA-256 da chave completa, nunca a chave em si.

Obter uma chave

As chaves e os webhooks são geridos por um administrador do salão em Definições -> API e Webhooks na app. Ao criar uma chave, escolhes um de três níveis de permissão:

PermissãoO que permite atualmente
read (predefinição)Apenas GET/HEAD/OPTIONS. Qualquer outro método em qualquer endpoint devolve 403.
writeNa prática, igual a read: atualmente nenhum endpoint aceita um pedido de escrita através de uma chave de API (consulta Endpoints de leitura abaixo, as seis rotas são todas GET).
fullHoje em dia, igual a read/write, pela mesma razão.

Em resumo: o acesso de escrita ainda não existe, independentemente do nível de permissão da chave. Não construas uma integração que espere criar ou alterar marcações, clientes, tratamentos ou profissionais através da API, todas as rotas que aceitam uma chave de API são apenas de leitura, e um pedido que tente algo diferente de GET em qualquer endpoint é rejeitado antes de chegar à lógica da rota.

A chave completa é mostrada exatamente uma vez, na resposta da chamada de criação da chave, no momento em que a crias. Guarda-a de imediato (num gestor de segredos, não numa mensagem de chat ou numa folha de cálculo), o Salonnare não consegue voltar a mostrar-te a chave. Se a perderes, revoga-a e cria uma nova.

Com que salão uma chave fala

É a própria chave que determina o salão (tenant), não o nome de anfitrião que chamas. Podes enviar pedidos para o subdomínio do teu próprio salão (https://<your-salon>.salonnare.com/api/...), que é a forma recomendada e inequívoca de o fazer, mas o salão na resposta é sempre aquele a que a chave pertence, independentemente de que anfitrião Salonnare válido acabes por acertar.

Erros de autenticação

SituaçãoEstadoCorpo
Sem cabeçalho X-API-Key e sem token Authorization: Bearer401{"error": "Token ontbreekt.", "code": "unauthorized", "error_key": "server.errors.token_missing"}
Chave desconhecida, malformada ou expirada401{"error": "Invalid API key.", "code": "invalid_api_key"}
O salão da chave não está ativo (por exemplo, suspenso)403{"error": "Salon is not active.", "code": "tenant_inactive"}
Pedido que não seja GET/HEAD/OPTIONS com uma chave read403{"error": "This API key is read-only.", "code": "read_only_key"}

Um pedido com uma chave de API recebe mensagens de erro em inglês: os três erros da camada de chave na tabela acima são sempre em inglês, e os erros que passam pelo mecanismo de tradução (a maioria dos que têm um campo error_key) são devolvidos em inglês para tráfego com chave, seja qual for o Accept-Language que envias. Um pedido sem chave (a primeira linha) segue o seu cabeçalho Accept-Language para os nove idiomas que o Salonnare suporta (nl, en, de, fr, es, it, pl, pt, tr) e vem em neerlandês nos restantes casos. Algumas mensagens noutros endpoints continuam a ser strings fixas em neerlandês. Não contes com nenhum dos dois comportamentos: faz a tua integração depender do código de estado HTTP (e de code/error_key quando presentes), nunca do texto da mensagem. Os corpos incluem sempre error; várias rotas adicionam code, e algumas adicionam ainda error_key/error_params - faz o parsing de forma tolerante e não assumas um conjunto de chaves exato e fixo.

Endpoints de leitura

Uma chave de API concede acesso de leitura a exatamente seis endpoints. Nenhuma outra rota aceita uma chave de API, independentemente do nível de permissão da chave:

MétodoCaminhoDevolve
GET/api/bookingsLista de marcações
GET/api/bookings/:idUma marcação
GET/api/crm/clientsLista de clientes
GET/api/crm/clients/:idUm cliente
GET/api/servicesLista de tratamentos
GET/api/staffLista de profissionais

Ao contrário do resto da app de administração do Salonnare, estas seis rotas não devolvem a linha bruta da base de dados a uma chave de API. Cada tipo de objeto passa por uma função de formatação fixa, toApiClient, toApiStaff, toApiBooking, toApiService, que emite sempre apenas uma lista de campos fixa e versionada, em snake_case. Nada fora dessa lista é incluído, independentemente do que o registo subjacente contenha ou venha a ganhar no futuro. Dois dos quatro formatos são intencionalmente idênticos à sua contrapartida de webhook, campo a campo: toApiClient corresponde ao payload de client.created/client.updated, e toApiBooking corresponde ao payload de marcação, incluindo o seu array services (ambos usam a mesma lista de permissões para as linhas de tratamento). Se já fazes o parsing de webhooks do Salonnare, as respostas REST de cliente e marcação não precisam de um parser separado.

Esta filtragem aplica-se apenas ao tráfego com chave de API. Os próprios profissionais do salão com sessão iniciada, ao chamarem exatamente os mesmos URLs com um token Bearer em vez de um cabeçalho X-API-Key, continuam a receber o registo completo e sem filtro de que a própria app depende (que inclui campos internos que estes formatadores eliminam deliberadamente). A diferença está puramente no canal de acesso, não no endpoint nem nos dados. Este documento só descreve o formato de X-API-Key, que é aquele que a tua integração vai ver.

GET /api/bookings

Parâmetros de consulta (todos opcionais):

ParâmetroFormatoEfeito
from, toYYYY-MM-DDIntervalo de datas semiaberto sobre a hora de início da marcação.
dateYYYY-MM-DDUm único dia (mesmo efeito que from=to=esse dia).
sourcestringFiltra pela forma como a marcação foi criada (manual, online, google_reserve, ...).
location_idintegerFiltra para uma localização.
client_idintegerFiltra para as marcações de um cliente.
pageintegerAtiva a paginação (ver abaixo). Sem isto, recebes o array completo.
limitintegerTamanho da página quando page está definido. Predefinição 50, máximo 200.

Sem ?page=, a resposta é um array JSON simples, limitado a 2000 linhas, ordenado por predefinição do mais recente para o mais antigo, exceto quando ordena do mais antigo para o mais recente (cronologicamente) se definires tanto from como to, ou se definires date. Com ?page=N, a resposta é:

{
  "items": [ /* mesmos objetos que abaixo */ ],
  "total": 134,
  "page": 1,
  "limit": 50,
  "pages": 3
}

Cada item, em ambos os modos, é o resultado de toApiBooking():

{
  "id": 4821,
  "client_id": 312,
  "staff_id": 7,
  "start_at": "2026-08-25T09:00:00.000Z",
  "end_at": "2026-08-25T09:45:00.000Z",
  "status": "scheduled",
  "notes": "",
  "services": [
    {
      "service_id": 41,
      "staff_id": 7,
      "duration_min": 45,
      "price": "45.00"
    }
  ]
}

Este é o conjunto de campos completo para este endpoint, sem nome do cliente, sem detalhes da sala, sem total ao nível da marcação. status é um de scheduled, provisional, completed, cancelled, no-show. As marcações canceladas e as faltas estão incluídas na lista, filtra-as do lado do cliente por status se só quiseres marcações ativas. notes é sempre uma string (string vazia, não null, quando não há nada a mostrar).

services são os tratamentos da marcação, pela ordem em que são realizados, e é o mesmo array que os webhooks de marcação enviam. Por linha: service_id (resolve o nome através de GET /api/services), staff_id (o profissional para esse tratamento específico, que pode ser diferente do staff_id da marcação), duration_min, e price, o preço como string decimal, registado no momento em que a marcação foi feita, para que uma alteração posterior ao preço do tratamento não reescreva o histórico. Uma marcação tem sempre pelo menos uma linha.

GET /api/bookings/:id

Sem parâmetros de consulta. Devolve um único objeto exatamente no mesmo formato que o item da lista acima:

{
  "id": 4821,
  "client_id": 312,
  "staff_id": 7,
  "start_at": "2026-08-25T09:00:00.000Z",
  "end_at": "2026-08-25T09:45:00.000Z",
  "status": "scheduled",
  "notes": "",
  "services": [
    {
      "service_id": 41,
      "staff_id": 7,
      "duration_min": 45,
      "price": "45.00"
    }
  ]
}

Não há aqui nome do cliente, sala, nem informação de programa (cure), a resposta com chave de API fica deliberadamente limitada aos oito campos acima, apesar de a versão deste endpoint para um utilizador com sessão iniciada devolver um objeto muito mais rico, com detalhe de sala por linha, profissionais extra por tratamento e o contexto de cure da marcação. services só contém tratamentos reais: as linhas de transição que a app usa para tempo de processamento (o tempo de oxidação de uma cor, por exemplo) ficam de fora, para que o array corresponda exatamente ao webhook de marcação. Adicionar campos mais tarde não é uma alteração que quebra compatibilidade, mas assume que a resposta de hoje tem exatamente este formato.

Um :id não numérico ou não positivo devolve 400 com {"error": "Invalid appointment ID.", "code": "bad_request", "error_key": "server.errors.invalid_booking_id_upper"}. Um :id válido mas inexistente devolve 404 com {"error": "Appointment not found.", "code": "not_found", "error_key": "server.errors.booking_not_found"}.

GET /api/crm/clients

Parâmetros de consulta (todos opcionais):

ParâmetroFormatoEfeito
searchstringCompara com nome, nome preferido, e-mail e telefone.
label_idintegerFiltra para clientes com esta etiqueta atribuída.
pageintegerAtiva a paginação, com o mesmo envelope das marcações acima.
limitintegerTamanho da página quando page está definido. Predefinição 50, máximo 200.

Sem ?page=, a resposta é um array JSON simples ordenado alfabeticamente (ou por relevância de pesquisa quando search está definido). Cada item é o resultado de toApiClient():

{
  "id": 312,
  "name": "Anna Voorbeeld",
  "email": "anna@example.com",
  "phone": null,
  "mobile": "+31612345678",
  "first_name": "Anna",
  "last_name": "Voorbeeld",
  "preferred_name": null,
  "city": "Waalwijk",
  "country": "NL",
  "created_at": "2026-01-14T10:22:00.000Z"
}

Este é o conjunto de campos completo, sem endereço, sem data de nascimento, sem etiquetas, sem dados de fidelização ou de faltas, e nenhum dos campos de conta/prestador de pagamentos (passwordHash, stripeCustomerId, e semelhantes) que antes passavam por aqui. search/label_id continuam a filtrar que clientes correspondem; não adicionam campos ao que é devolvido sobre cada um.

GET /api/crm/clients/:id

Sem parâmetros de consulta. Devolve um único objeto exatamente no mesmo formato que o item da lista acima:

{
  "id": 312,
  "name": "Anna Voorbeeld",
  "email": "anna@example.com",
  "phone": null,
  "mobile": "+31612345678",
  "first_name": "Anna",
  "last_name": "Voorbeeld",
  "preferred_name": null,
  "city": "Waalwijk",
  "country": "NL",
  "created_at": "2026-01-14T10:22:00.000Z"
}

As respostas do formulário de admissão (intake_responses) nunca fazem parte desta resposta para uma chave de API, independentemente de quem tem permissão para as ver na app, o campo simplesmente não está na lista fixa acima.

Um :id não numérico ou zero/NaN devolve 400 com {"error": "Invalid client ID.", "code": "bad_request", "error_key": "server.errors.invalid_client_id"}. Um :id válido mas inexistente devolve 404 com {"error": "Client not found.", "code": "not_found", "error_key": "server.errors.not_found", "error_params": {"entity": "Client"}}.

GET /api/services

Parâmetros de consulta:

ParâmetroFormatoEfeito
bookable1 ou trueApenas tratamentos ativos (não arquivados). Sem isto, os tratamentos arquivados também são incluídos.

A resposta é sempre um array JSON simples (sem suporte de paginação neste endpoint). Cada item é o resultado de toApiService():

[
  {
    "id": 41,
    "name": "Knipbeurt",
    "duration_min": 45,
    "price": "45.00",
    "category_id": 3,
    "active": true
  }
]

Este é o conjunto de campos completo, sem descrição, sem número de artigo, sem taxa de IVA, sem restrições de marcação, e sem nome/cor da categoria (apenas o category_id em bruto; se precisares dos detalhes da categoria, obtém-los separadamente ou mantém o teu próprio mapeamento).

GET /api/staff

Sem parâmetros de consulta. A resposta é sempre um array JSON simples (sem suporte de paginação). Cada item é o resultado de toApiStaff():

[
  {
    "id": 7,
    "name": "Chris Medewerker",
    "email": "chris@example.com",
    "role": "staff"
  }
]

Este é o conjunto de campos completo, sem iban/bic, sem estado do Stripe Connect, sem estado de bloqueio/tentativas de início de sessão, sem flags de permissão de páginas. Se a tua integração precisar de detalhes de pagamento, isso não é algo que uma chave de API possa ler; tem de vir diretamente do salão.

Não existe propositadamente nenhum campo active: o endpoint só devolve os profissionais ativos do salão, por isso o campo nunca poderia ser outra coisa além de true. Não esperes por um active: false para dares um profissional como saído, um profissional que sai simplesmente deixa de aparecer nesta lista, por isso trata "estar ausente da resposta" como o sinal.

Limite de taxa

Todos os pedidos /api/*, incluindo os pedidos com chave de API, estão limitados a 200 pedidos por minuto por combinação de salão e endereço IP. Nenhum dos seis endpoints de leitura tem um limitador adicional, mais estrito, por cima deste.

Quando o excedes, obténs:

  • Estado 429
  • Corpo: {"error": "Too many requests, please try again later", "code": "rate_limited", "error_key": "server.errors.rate_limited_comma"}
  • Cabeçalhos em cada resposta (não só quando limitado): X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (timestamp Unix, em segundos)
  • No próprio 429, um cabeçalho adicional Retry-After com o número de segundos a esperar antes de tentar novamente

Respeita o Retry-After em vez de fazeres polling num intervalo fixo, ele reflete o momento real de reset da janela que atingiste.

Webhooks

Em vez de fazer polling, podes registar um URL e fazer com que o Salonnare envie eventos para ele à medida que acontecem. Os webhooks são configurados na app em Definições -> API e Webhooks -> Webhooks, o mesmo sítio onde vivem as chaves de API.

Envelope

Cada entrega é um POST com este corpo JSON:

{
  "event": "booking.created",
  "data": { /* payload específico do evento, ver abaixo */ },
  "timestamp": "2026-08-25T09:00:03.512Z"
}

Além do corpo, cada entrega transporta dois cabeçalhos: X-Webhook-Signature (consulta Verificar assinaturas) e X-Webhook-Event (o mesmo valor que event no corpo).

Eventos

Podes subscrever qualquer combinação dos seis eventos. Lê a coluna "Dispara quando" à letra, cada um destes eventos só dispara a partir de uma ação de um profissional do salão na app, e cada um tem exatamente um ponto de acionamento no servidor:

EventoDispara quando
booking.createdUm profissional cria uma marcação na app (POST /api/bookings)
booking.updatedUm profissional edita uma marcação na app (PATCH/PUT /api/bookings/:id), incluindo uma alteração de estado para concluída ou falta, já que a app faz isso através da mesma chamada
booking.cancelledUm profissional cancela uma marcação na app (DELETE /api/bookings/:id)
payment.receivedUm profissional conclui um pagamento na caixa (POST /api/pos/orders)
client.createdUm profissional cria um cliente na app (POST /api/crm/clients)
client.updatedUm profissional edita um cliente na app (PATCH/PUT /api/crm/clients/:id)

O que não dispara um evento

Esta é a parte que apanha as integrações desprevenidas, por isso está explicitada em vez de implícita. Marcações, clientes e pagamentos que surgem de qualquer outra forma não produzem webhook nenhum. Concretamente, nada é enviado quando:

  • um cliente faz uma marcação online através da página de marcações do salão, ou cancela ou confirma ali uma marcação ele próprio;
  • uma marcação chega através do Google Reserve;
  • uma entrada na lista de espera é convertida numa marcação;
  • uma série recorrente gera as suas marcações;
  • é criada uma série de um programa (cure), ou é cancelada uma sessão de uma;
  • uma marcação provisória é cancelada automaticamente porque o prazo do sinal passou, ou é promovida a confirmada porque o sinal foi pago;
  • uma marcação é marcada como concluída como efeito secundário de um pagamento na caixa;
  • um cliente se cria a si próprio na página de marcações, se regista através do Google/Microsoft/Apple/Facebook, ou é criada por uma importação;
  • um registo de cliente é alterado por algo diferente do ecrã de edição de clientes, recálculo do nível de fidelização, saldo, respostas do formulário de admissão, um e-mail devolvido, ou é anonimizado por um pedido de eliminação.

A consequência prática: não podes usar os webhooks como um feed completo de alterações. Para um salão que aceita marcações online, a maioria das novas marcações nunca vai chegar ao teu endpoint. Reconcilia periodicamente com GET /api/bookings usando uma janela from/to e trata os webhooks como uma indicação de baixa latência além disso, não como a fonte de verdade.

Os seis payloads passam todos por uma lista de permissões de campos fixa no servidor, só os campos listados abaixo são enviados, em snake_case, independentemente do que mais o registo subjacente contenha. Isto é um contrato deliberado: os payloads de webhook não vão ganhar silenciosamente novos campos a partir da base de dados.

booking.created / booking.updated / booking.cancelled:

{
  "event": "booking.created",
  "data": {
    "id": 4821,
    "client_id": 312,
    "staff_id": 7,
    "start_at": "2026-08-25T09:00:00.000Z",
    "end_at": "2026-08-25T09:45:00.000Z",
    "status": "scheduled",
    "notes": "",
    "services": [
      {
        "service_id": 41,
        "staff_id": 7,
        "duration_min": 45,
        "price": "45.00"
      }
    ]
  },
  "timestamp": "2026-08-25T09:00:03.512Z"
}

Os três eventos de marcação enviam exatamente este formato, construído a partir da marcação guardada: os mesmos campos, os mesmos tipos, start_at/end_at sempre em UTC. Dois detalhes a saber:

  • status é o estado no momento em que o evento é enviado. Um booking.created para uma marcação que precisa de sinal reporta, por isso, provisional, não scheduled, e um booking.cancelled reporta cancelled em vez do estado que a marcação tinha antes.
  • Este é o mesmo conjunto de campos que GET /api/bookings/:id, incluindo o array services, por isso um único parser cobre ambos os canais.

payment.received:

{
  "event": "payment.received",
  "data": {
    "order_id": 9931,
    "order_number": "2026-000482",
    "total": 45,
    "payment_method": "card",
    "client_id": 312
  },
  "timestamp": "2026-08-25T09:12:47.001Z"
}

client.created / client.updated:

{
  "event": "client.created",
  "data": {
    "id": 312,
    "name": "Anna Voorbeeld",
    "email": "anna@example.com",
    "phone": null,
    "mobile": "+31612345678",
    "first_name": "Anna",
    "last_name": "Voorbeeld",
    "preferred_name": null,
    "city": "Waalwijk",
    "country": "NL",
    "created_at": "2026-01-14T10:22:00.000Z"
  },
  "timestamp": "2026-08-25T09:00:03.512Z"
}

Nota que este é exatamente o mesmo conjunto de campos que a resposta de GET /api/crm/clients acima, toWebhookClient() e toApiClient() partilham a mesma lista de permissões, por isso um cliente tem o mesmo aspeto quer o recebas via webhook quer o obtenhas via API.

Quando um proprietário de salão envia uma entrega de teste manual a partir do ecrã de Webhooks, recebes um envelope com "event": "test" e um pequeno payload de exemplo. Não é um dos seis eventos subscritíveis, faz com que o teu handler ignore (sem dar erro) nomes de eventos que não reconheça.

Verificar assinaturas

Cada entrega é assinada com HMAC-SHA256 sobre o corpo bruto do pedido, usando o segredo do webhook. Tal como uma chave de API, este segredo é encriptado em repouso e só é mostrado na íntegra uma vez: na resposta da chamada de criação, e de novo se o salão o regenerar através de POST /api/developer/webhooks/:id/rotate-secret. GET/PUT num webhook nunca incluem o segredo, apenas um booleano hasSecret, por isso, se o salão o tiver perdido, tem de o regenerar (ou tu, em nome dele) e atualizar o que quer que verifique a assinatura do teu lado. O digest hexadecimal é enviado no cabeçalho X-Webhook-Signature.

const crypto = require('crypto');

function verify(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

rawBody tem de ser os bytes exatos que o Salonnare enviou na rede - não JSON.stringify(JSON.parse(rawBody)), nem qualquer objeto que tenhas voltado a serializar. Reserializar altera a ordem das chaves e os espaços em branco, o que altera os bytes, o que altera a assinatura. Este é o erro de integração mais comum de todos, e produz uma assinatura que nunca corresponde, sem nenhuma mensagem de erro clara a apontar para a causa. Lê o corpo como uma string/buffer bruta antes de a tua framework o fazer parsing como JSON, e calcula o hash a partir disso.

crypto.timingSafeEqual lança um erro (em vez de devolver false) quando os dois buffers diferem em tamanho, por isso um cabeçalho X-Webhook-Signature ausente ou malformado vai fazer o verify() acima falhar com um crash em vez de simplesmente falhar a verificação. Protege-te contra isso, confirma que o cabeçalho está presente e é uma string hexadecimal de 64 carateres antes de chamar timingSafeEqual, ou envolve a chamada num try/catch.

Comportamento das entregas

  • Uma tentativa, sem repetições. Se o teu endpoint estiver em baixo ou der erro, o Salonnare não coloca essa entrega em fila nem a repete. Desenha a tua integração para tolerar um evento ocasionalmente perdido (por exemplo, reconciliando periodicamente com GET /api/bookings usando uma janela from/to) em vez de assumires que todos os eventos vão chegar.
  • Tempo limite de 10 segundos. Se o teu endpoint não responder dentro de 10 segundos, a entrega é tratada como falhada.
  • Os redirecionamentos não são seguidos. Uma resposta 3xx conta como uma entrega falhada, exatamente como um 4xx/5xx. Responde 2xx a partir do URL exato que registaste.
  • Desativação automática após 10 falhas consecutivas. Depois de um webhook falhar 10 vezes consecutivas (contam tempos limite, respostas que não sejam 2xx, e URLs bloqueados/inválidos), o Salonnare desativa-o automaticamente. O proprietário do salão tem de o reativar a partir do ecrã de Webhooks; reativar repõe o contador de falhas.
  • Os registos de entrega estão disponíveis para o salão, não para o teu endpoint. Um administrador do salão pode inspecionar as últimas entregas (código de estado, corpo da resposta, duração) de um webhook através de GET /api/developer/webhooks/:id/logs na app, esta chamada exige uma sessão de administrador (não uma chave de API), por isso serve para o salão depurar a sua própria integração, não é algo que o teu serviço recetor chame.

Requisitos do URL

O URL do webhook que registas tem de estar acessível publicamente pela internet. O Salonnare valida-o tanto quando o guardas como de novo, de forma atualizada, imediatamente antes de cada entrega (para se proteger contra um domínio que hoje resolve para algo seguro e amanhã para algo interno). Um URL é rejeitado, no momento de guardar com um 400, ou no momento da entrega como uma tentativa falhada, quando é ou resolve para:

  • Qualquer coisa que não seja http:/https: (em produção, apenas https:)
  • localhost, 127.0.0.1, 0.0.0.0, ::1, :: ou qualquer equivalente IPv6 entre parênteses retos
  • Um nome de anfitrião interno (server, db, mariadb, mysql, redis, docs, worker)
  • Um endereço IP literal privado/loopback/link-local (intervalos RFC 1918, 169.254.0.0/16, 127.0.0.0/8, IPv6 ULA fc00::/7, IPv6 link-local fe80::/10)
  • Um endereço IPv6 numa das notações de transição que pode esconder um endereço IPv4 nos seus bits inferiores: IPv4-mapped (::ffff:0:0/96), IPv4-compatible (::/96), NAT64 (64:ff9b::/96) e 6to4 (2002::/16). Estes são bloqueados pelo prefixo, por isso todas as formas de escrever o endereço incorporado são rejeitadas, [::ffff:127.0.0.1], [::127.0.0.1] e [::7f00:1] por igual. O IPv6 global-unicast normal não é afetado
  • Um nome de anfitrião público cujo registo DNS resolve para qualquer um dos anteriores, verificado no momento da entrega, não só uma vez no registo, especificamente para que um domínio que apontes para um endereço interno mais tarde continue a ser bloqueado

Aponta o URL para um domínio que controlas desde o início; não registes um placeholder à espera de redirecionar o DNS para um endereço interno mais tarde, e não dependas só da verificação no momento de guardar, a entrega volta a validar sempre.

Artigos relacionados