marketing, retention e collection falham fechadas mesmo com a Niadra fora do alcance, e é a única barreira que a Niadra não é.
Antes de começar
- A funcionalidade
coordinationligada no espaço. - O gateway declarado no documento
coordination: umgateway_id(^[a-z][a-z0-9_]{0,39}$), os canais que ele serve esecret_ref, a referência à chave de 32 bytes que ele compartilha com a Niadra, gravada no cofre do espaço porPUT /v1/secrets/{kind}/{secret_id}. Um canal com um gateway só não precisa degateway_idna pergunta; com vários, a pergunta nomeia um, ou é recusada. - As finalidades que precisam de token, em
purposes[].token:marketing,retentionecollectionpor padrão. Mensagens do cliente,transactionaleservicenunca precisam. - Uma chave de fonte para o gateway, com que ele lê as chaves públicas do espaço (a rota é pública) e, se quiser, declara
contact.made(escopocoordinate).
O caminho de uma mensagem
- O agente pergunta (
POST /v1/coordination/check) comdirection: outbound, a finalidade, o canal e, quando há mais de um gateway no canal, ogateway_id. Umallowde uma finalidade que precisa de token trazcontact_token. - O agente entrega a mensagem ao gateway com o token num cabeçalho ou num campo de metadado do pedido de despacho, nunca na URL.
- O gateway confere o token contra o canal e o destino da mensagem, deixa a mensagem sair uma vez e declara
contact.madecom ojtido token. - Uma mensagem sem token, ou com token recusado, não sai. O agente não tenta o mesmo token de novo: pede uma decisão nova.
O token
kid (a chave que assinou), space, jti (o id do token, um UUIDv7 igual ao decision_id da pergunta), purpose, channel, rcpt, gateway, iat e exp (em segundos Unix, exp até 120 segundos depois de iat). A assinatura é Ed25519 sobre os bytes ASCII de nct1.<payload>, como aparecem no token. Nada nele é dado pessoal: o destino vai só como rcpt, o HMAC-SHA256 em base64url da forma canônica do destino (phone:+5511987654321, email:ana@example.com) com a chave compartilhada do gateway, que só o gateway e a Niadra têm. Um token tem no máximo 1.024 caracteres.
As chaves públicas
GET /.well-known/niadra-contact-keys.json?space=<id do espaço> devolve o conjunto de JWKs do espaço (RFC 8037: kty: OKP, crv: Ed25519, x, kid, space, status, not_after). Uma chave é active ou retiring: a em retirada não assina nada novo e ainda verifica o que assinou, até not_after; a Niadra gira publicando a próxima como ativa e a anterior em retirada por ao menos a vida de um token. Guarde o conjunto em cache, renove ao menos de hora em hora e, diante de um kid desconhecido, renove no máximo uma vez por minuto antes de recusar. A resposta vem com Cache-Control: public, max-age=300. Um gateway nunca para de conferir porque a Niadra está fora do alcance: ele fica com o último conjunto que tem, por mais velho que esteja, e recusa o token que não consegue conferir.
Conferir
Os SDKs trazem a conferência, com as chaves lidas e guardadas por você:malformed, unsupported_version, unknown_key, bad_signature, wrong_space, wrong_gateway, lifetime_too_long, not_yet_valid (iat mais de 5 segundos no futuro), expired (exp passou, com 5 segundos de folga), wrong_channel, wrong_recipient (comparação em tempo constante) e replayed. Depois de aceitar, ele lembra o jti até exp + 5 segundos e o recusa de novo; um gateway com vários processos compartilha essa memória (a interface SeenTokens em Python e em TypeScript, com add(jti, until)) ou roteia as mensagens de um token para um processo só. MemorySeen é a memória de um processo. Um destino que não é telefone nem e-mail válido é invalid_handle, e um tipo de handle fora da lista, unsupported_type.
Um gateway que não roda os SDKs confere os mesmos passos com qualquer biblioteca Ed25519 e HMAC: a especificação aberta spec/contact-token.md, no repositório público niadra-spec, fixa cada passo, e vectors/contact-token.v0.json traz tokens de teste com o resultado esperado de cada um, com uma chave privada de teste, nunca uma real.
Declarar o contato
contact.made fecha o ciclo: com o jti do token, o contato conta uma vez no registro de contatos e no orçamento da finalidade; dois contact.made com o mesmo jti mostram um token usado duas vezes, que o relatório de sombra conta em tokens_used_twice. Uma mensagem que saiu numa pergunta que a Niadra não respondeu (service fora do alcance) é declarada com unchecked: true, e conta no orçamento mesmo assim. O agente que faz a pergunta pode declarar em vez do gateway (conversation.declare.contact_made(decision, ...) nos SDKs); o que importa é que alguém declare.
Quando o despacho é de terceiro
Onde o ponto de despacho é um software de terceiro que não confere token, a coordenação é sombra mais supressão: o relatório de sombra conta os conflitos que uma pergunta teria apontado, e a lista de supressão continua sendo o piso que o terceiro honra por conta própria, lendo-a a cada 60 segundos.Próximos passos
Coordenação
a pergunta, a decisão e os efeitos que acontecem uma vez só.
Chaves públicas do token de contato
a referência da rota pública.
Declarar o que aconteceu
contact.made e as outras declarações.Agentes de varejo
um orçamento de marketing e o gateway de WhatsApp.

