signingKey é secreta e não pode ir para o navegador.| Campo | Exemplo | Uso |
|---|---|---|
ref | attrion | Identificador público do parceiro. Vai na URL final e no payload assinado. |
signingKey | <chave-gerada-pelo-Phantom> | Chave secreta em hexadecimal usada para gerar sig. Fica somente no backend do parceiro. |
ttlDays | 7 | Validade máxima do link, em dias. Pode ser menor que o limite configurado. |
destinationUrl | https://phantomcloak.app/plans | URL do Phantom para onde o usuário será enviado. |
| Campo | Como gerar | Exemplo |
|---|---|---|
exp | Math.floor(Date.now() / 1000) + ttlDays * 24 * 60 * 60 | 1771604800 |
payload | <ref>:<exp> | partner-slug:1771604800 |
sig | base64url(HMAC-SHA256(payload, hexToBytes(signingKey))) | Ks2LxH... |
url | <destinationUrl>?ref=<ref>&sig=<sig>&exp=<exp> | https://phantomcloak.app/plans?ref=partner-slug&sig=Ks2LxH...&exp=1771604800 |
sig. Ele não vai na URL. O formato deve ser exatamente <ref>:<exp>. Não assine JSON, a URL completa nem adicione espaços.POST /partners/validate-link. No fluxo real, o usuário é redirecionado para o site do Phantom e o Phantom valida o link automaticamente.couponCode e partnerAttributionToken. Esses dois campos devem seguir juntos para o checkout quando o cupom exige assinatura de parceiro.curl --location '/partners/validate-link' \
--header 'Content-Type: application/json' \
--data '{
"url": "https://phantomcloak.app/plans?ref=partner-slug&sig=Ks2LxH...&exp=1771604800"
}'{
"valid": true,
"ref": "partner-slug",
"partnerId": "550e8400-e29b-41d4-a716-446655440001",
"partnerName": "Parceiro Exemplo",
"couponId": "550e8400-e29b-41d4-a716-446655440002",
"couponCode": "PARCEIRO10",
"expiresAt": "2026-05-25T20:25:27.000Z",
"expiresInSeconds": 604800,
"partnerAttributionToken": "v1.eyJwYXJ0bmVySWQiOiIuLi4ifQ.signature"
}