aflink.

AFLINK / API

Conectar seu SaaS ao Aflink

Este guia mostra o caminho mais curto para seu SaaS pagar comissões a afiliados pelo Aflink. A integração tem três partes e nenhuma delas fica no caminho do seu checkout ou do carregamento das suas páginas:

  1. Clique — o afiliado divulga um link https://aflink.app/r/{código}. O Aflink registra o clique e redireciona o visitante para o seu site com ?_aflink=att_….
  2. Captura — seu site guarda esse token junto do cadastro do cliente.
  3. Eventos — quando o cliente indicado paga, renova ou é reembolsado, seu servidor envia um evento assinado ao Aflink. A comissão é calculada pela política do seu programa.

Sem script de terceiros, sem pixel e sem chamada síncrona ao Aflink no checkout.

  1. Entre em aflink.app com sua Conta CIATTO e abra sua empresa.
  2. Programas → Criar programa: informe o endereço do seu site (o destino dos links), a política de comissão e os termos. Os valores sugeridos (15%, 12 pagamentos mensais, 30 dias de atribuição, 30 dias de retenção) podem ser alterados.
  3. Integrações → Nova credencial: crie uma credencial TEST para validar e, depois, uma LIVE para produção. A chave de API e o segredo de assinatura aparecem uma única vez: guarde-os no cofre de segredos do seu servidor, nunca no navegador, no app ou no repositório.

Use o modelo de pagamentos mensais (padrão) sempre que possível: ele não exige enviar janelas de serviço. O modelo por meses de serviço está descrito no final.

1. Capture o token do clique

O visitante chega ao seu site com ?_aflink=att_…. Guarde o token por, no máximo, o prazo de atribuição do programa (30 dias por padrão). Se chegar um token novo, ele substitui o anterior (vale o último clique) até o primeiro pagamento.

Opção A — no servidor (recomendada)

No middleware que atende suas páginas públicas:

se a URL tem _aflink e ele casa com ^att_[A-Za-z0-9_-]{20,128}$:
    gravar cookie aflink_att=<token>; Max-Age=2592000; Path=/; Secure; HttpOnly; SameSite=Lax
    (use Domain=.seudominio.com se o cadastro acontece em outro subdomínio)

Opção B — no navegador

Um trecho inline de poucas linhas, sem dependência e sem rede. Coloque-o na página de destino:

<script>
  (function () {
    try {
      var t = new URLSearchParams(location.search).get("_aflink");
      if (!t || !/^att_[A-Za-z0-9_-]{20,128}$/.test(t)) return;
      document.cookie = "aflink_att=" + t + "; Max-Age=2592000; Path=/; SameSite=Lax; Secure";
      var u = new URL(location.href);
      u.searchParams.delete("_aflink");
      history.replaceState(null, "", u);
    } catch (e) {}
  })();
</script>

No cadastro

Quando a conta for criada, leia o cookie aflink_att no servidor e grave o token no registro do cliente, por exemplo nas colunas aflink_token e aflink_token_at. Sem token, o cliente não foi indicado e nenhum evento deve ser enviado para ele.

2. Escolha os eventos

Envie eventos somente de clientes indicados, ou seja, que têm token ou cupom de afiliado. Isso reduz volume e evita compartilhar dados de quem não veio de um afiliado.

Quando acontece no seu SaaSEventoCampos obrigatórios além dos comuns
Primeiro pagamento de uma assinaturasubscription.startedattributionToken (ou couponCode), externalSubscriptionId, customerExternalId, occurredAt
Cada pagamento seguinte da assinaturasubscription.renewedexternalSubscriptionId, customerExternalId, subscriptionCycle (2, 3, …), occurredAt
Compra avulsaorder.paidattributionToken (ou couponCode), customerExternalId
Reembolso total ou parcialrefund.createdoriginalExternalPaymentId
Chargebackchargeback.createdoriginalExternalPaymentId

Campos comuns a todos os eventos:

CampoRegra
externalPaymentIdID do pagamento no seu sistema ou no gateway (3–255 caracteres). Único por pagamento.
customerEmailE-mail do cliente.
customerExternalIdID estável do cliente no seu SaaS, nunca o e-mail (até 255). Ex.: seusaas:conta_123.
valueValor sobre o qual a comissão é calculada, como texto decimal: "189.00". Sempre positivo, inclusive em reembolsos (até 2 casas).
currencyCódigo de 3 letras igual ao do programa, ex.: BRL.
occurredAtMomento do pagamento em RFC 3339 (UTC). Não pode estar mais de 5 min no futuro.
metadataOpcional. Não coloque dados pessoais.

O primeiro pagamento amarra o cliente ao afiliado de forma permanente: renovações e reembolsos são reconhecidos pelo customerExternalId e pelo originalExternalPaymentId, sem precisar do token de novo. Envie o pagamento original antes do seu reembolso.

Exemplo de subscription.started:

{
  "type": "subscription.started",
  "data": {
    "attributionToken": "att_AbCdEf…",
    "customerExternalId": "seusaas:conta_123",
    "customerEmail": "[email protected]",
    "externalPaymentId": "in_1Q2w3E",
    "externalSubscriptionId": "sub_9Z8y7X",
    "value": "189.00",
    "currency": "BRL",
    "occurredAt": "2026-10-22T14:30:00Z"
  }
}

3. Assine e envie

POST https://api.aflink.dev/v1/events com Content-Type: application/json e:

CabeçalhoValor
AuthorizationBearer <chave de API>
Idempotency-KeyChave determinística do evento, ex.: subscription.started:in_1Q2w3E (8–255).
X-Aflink-TimestampUnix em segundos (10 dígitos). Relógio com até 5 min de diferença.
X-Aflink-NonceAleatório novo a cada tentativa, 16–128 caracteres [A-Za-z0-9_-].
X-Aflink-Signaturev1= + hex de HMAC-SHA256(segredo de assinatura, timestamp + "." + nonce + "." + corpo).

A assinatura usa os bytes exatos do corpo enviado. O corpo tem limite de 64 KB. Chamadas feitas do navegador (com Cookie ou Origin) são recusadas.

Node.js 18+

import crypto from "node:crypto";

export async function sendAflinkEvent(idempotencyKey, body) {
  // body: string JSON exata, guardada no outbox (não serialize de novo).
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomBytes(24).toString("base64url");
  const signature = crypto
    .createHmac("sha256", process.env.AFLINK_SIGNING_SECRET)
    .update(`${timestamp}.${nonce}.${body}`)
    .digest("hex");
  return fetch("https://api.aflink.dev/v1/events", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.AFLINK_API_KEY}`,
      "Idempotency-Key": idempotencyKey,
      "X-Aflink-Timestamp": timestamp,
      "X-Aflink-Nonce": nonce,
      "X-Aflink-Signature": `v1=${signature}`,
    },
    body,
    signal: AbortSignal.timeout(10_000),
  });
}

Python 3

import hashlib, hmac, os, secrets, time, urllib.request

def send_aflink_event(idempotency_key: str, body: bytes):
    timestamp = str(int(time.time()))
    nonce = secrets.token_urlsafe(24)
    message = timestamp.encode() + b"." + nonce.encode() + b"." + body
    signature = hmac.new(os.environ["AFLINK_SIGNING_SECRET"].encode(), message, hashlib.sha256).hexdigest()
    request = urllib.request.Request(
        "https://api.aflink.dev/v1/events", data=body, method="POST",
        headers={
            "Content-Type": "application/json",
            "Authorization": "Bearer " + os.environ["AFLINK_API_KEY"],
            "Idempotency-Key": idempotency_key,
            "X-Aflink-Timestamp": timestamp,
            "X-Aflink-Nonce": nonce,
            "X-Aflink-Signature": "v1=" + signature,
        })
    return urllib.request.urlopen(request, timeout=10)

Go

func sendAflinkEvent(ctx context.Context, idempotencyKey string, body []byte) (*http.Response, error) {
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	raw := make([]byte, 24)
	if _, err := rand.Read(raw); err != nil {
		return nil, err
	}
	nonce := base64.RawURLEncoding.EncodeToString(raw)
	mac := hmac.New(sha256.New, []byte(os.Getenv("AFLINK_SIGNING_SECRET")))
	mac.Write([]byte(timestamp + "." + nonce + "."))
	mac.Write(body)
	req, err := http.NewRequestWithContext(ctx, "POST", "https://api.aflink.dev/v1/events", bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+os.Getenv("AFLINK_API_KEY"))
	req.Header.Set("Idempotency-Key", idempotencyKey)
	req.Header.Set("X-Aflink-Timestamp", timestamp)
	req.Header.Set("X-Aflink-Nonce", nonce)
	req.Header.Set("X-Aflink-Signature", "v1="+hex.EncodeToString(mac.Sum(nil)))
	return (&http.Client{Timeout: 10 * time.Second}).Do(req)
}

PHP

function send_aflink_event(string $idempotencyKey, string $body) {
    $timestamp = (string) time();
    $nonce = rtrim(strtr(base64_encode(random_bytes(24)), '+/', '-_'), '=');
    $signature = hash_hmac('sha256', $timestamp . '.' . $nonce . '.' . $body, getenv('AFLINK_SIGNING_SECRET'));
    $ch = curl_init('https://api.aflink.dev/v1/events');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $body,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            'Authorization: Bearer ' . getenv('AFLINK_API_KEY'),
            'Idempotency-Key: ' . $idempotencyKey,
            'X-Aflink-Timestamp: ' . $timestamp,
            'X-Aflink-Nonce: ' . $nonce,
            'X-Aflink-Signature: v1=' . $signature,
        ],
    ]);
    $response = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return [$status, $response];
}

4. Entregue sem perder dados e sem pesar seu sistema

Não chame o Aflink dentro da requisição de pagamento. Use um outbox:

  1. Na mesma transação que grava o pagamento no seu banco, grave o evento numa tabela de saída com a Idempotency-Key e o corpo JSON exato que será enviado.
  2. Um job em segundo plano envia os pendentes. A cada tentativa gera timestamp, nonce e assinatura novos, mas mantém a mesma chave e o mesmo corpo.
  3. Marque como entregue ao receber 202 (aceito) ou 200 (já recebido antes).
CREATE TABLE aflink_outbox (
  idempotency_key text PRIMARY KEY,
  body            text NOT NULL,          -- bytes exatos; nunca reserializar
  attempts        int  NOT NULL DEFAULT 0,
  next_attempt_at timestamptz NOT NULL DEFAULT now(),
  delivered_at    timestamptz,
  last_error      text
);
-- job: SELECT … WHERE delivered_at IS NULL AND next_attempt_at <= now()
--      ORDER BY next_attempt_at LIMIT 50 FOR UPDATE SKIP LOCKED

Reenviar é sempre seguro: a mesma chave com o mesmo corpo é deduplicada, e o mesmo externalPaymentId nunca gera duas comissões.

RespostaO que fazer
202 ou 200Entregue. O processamento continua no Aflink.
409 request_replayNonce repetido. Tente de novo com nonce novo.
429 rate_limitedAguarde o Retry-After (60 s). Limite: 300 eventos/min por origem.
5xx, timeout ou erro de redeTente de novo com espera crescente (1, 5, 15, 60 min… por até 3 dias).
400 invalid_integration_headersCorrija relógio, nonce ou chave de idempotência e reenvie.
400 invalid_eventDados inválidos. Não repita; corrija o evento e alerte.
401 invalid_credential / invalid_signatureChave revogada ou segredo errado. Pause o envio e alerte.
409 idempotency_conflictMesma chave com corpo diferente: erro na sua integração. Não repita.
413 event_too_largeCorpo acima de 64 KB. Remova metadata desnecessário.

5. Teste e entre em produção

  1. Configure a credencial TEST e envie um subscription.started usando um token real (clique em um link de teste do seu programa). Eventos TEST nunca geram comissão.
  2. Em Integrações → Recibos de eventos, confira o status:
    • PROCESSED: tudo certo.
    • FAILED com processing_requirements_not_met: o evento não corresponde a uma atribuição válida. Verifique o token, o prazo de atribuição, a moeda, se o programa está ativo e se o afiliado foi aprovado.
    • RECEIVED com original_payment_pending: o reembolso chegou antes do pagamento original; o Aflink tenta novamente por cerca de 10 minutos.
  3. Troque para a credencial LIVE.

Checklist de produção

Programas por meses de serviço (avançado)

Se o programa paga comissão por meses de serviço (por exemplo, durante o primeiro ano), cada evento de assinatura precisa de serviceWindow:

"serviceWindow": {
  "start": "2026-10-22T14:30:00Z",
  "end": "2027-10-22T14:30:00Z",
  "components": [
    { "id": "plano-anual", "start": "2026-10-22T14:30:00Z", "end": "2027-10-22T14:30:00Z", "amountMinor": 561600 }
  ]
}

Prefira o modelo de pagamentos mensais quando não precisar dessa precisão.

Referência

Ir para o Aflink