> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appreval.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Receitas: integrações típicas com a API e os webhooks

> Guias passo a passo para os casos mais comuns: sincronizar um warehouse, conciliar pedidos, um bot de suporte que pausa e cancela, alertas de cobranças recusadas, Zapier/Make/n8n e emails de retenção com link para o portal.

Cada receita combina o que as páginas da [API](https://docs.appreval.com/pt/desenvolvedores/api.md) e dos [webhooks](https://docs.appreval.com/pt/desenvolvedores/webhooks.md) já explicam para resolver um caso concreto de ponta a ponta. Os exemplos usam `curl` e Python porque são fáceis de ler; qualquer linguagem com HTTP e JSON serve. Todos os tipos e campos estão na [referência do esquema](https://docs.appreval.com/pt/desenvolvedores/api-referencia.md).

> [!TIP]
>
> Antes de começar, gere uma chave em **Reval → Integrações** e teste a [primeira consulta](https://docs.appreval.com/pt/desenvolvedores/api.md#primeira-consulta). Para as receitas que só leem, use uma chave **somente leitura**.

***

## 1. Sincronizar as assinaturas com o seu warehouse

**Objetivo:** manter no BigQuery, Snowflake, Postgres ou numa planilha uma cópia das assinaturas que se atualiza sozinha, sem trazer tudo a cada vez.

**Como:** a primeira execução traz tudo; as seguintes pedem só o que mudou desde a última, com `updatedAfter` e ordenação por `UPDATED_AT`. Guarde a data da última execução e o `endCursor` para o caso de uma página falhar no meio.

```graphql theme={null}
query Sync($after: String, $since: DateTime) {
  subscriptions(
    first: 100
    after: $after
    filter: { updatedAfter: $since }
    sortKey: UPDATED_AT
    reverse: true
  ) {
    nodes {
      id status customerEmail shopifyCustomerId
      amount currency frequency frequencyType nextPaymentDate
      plan { id name } planGroup { id title }
      items { shopifyVariantId quantity unitAmount role }
      shippingAddress { city province countryCode }
      createdAt updatedAt
    }
    pageInfo { hasNextPage endCursor }
  }
}
```

```python theme={null}
import requests, datetime

API = "https://api.appreval.com/api/graphql"
HEADERS = {"Authorization": "Bearer reval_XXXXXXXX", "Content-Type": "application/json"}

def fetch_changed(since: str):
    after = None
    while True:
        r = requests.post(API, headers=HEADERS, json={"query": QUERY, "variables": {"after": after, "since": since}})
        r.raise_for_status()
        page = r.json()["data"]["subscriptions"]
        yield from page["nodes"]
        if not page["pageInfo"]["hasNextPage"]:
            return
        after = page["pageInfo"]["endCursor"]

started = datetime.datetime.now(datetime.timezone.utc).isoformat()
for sub in fetch_changed(last_run):   # last_run: o que você guardou da vez anterior
    upsert(sub)                       # seu warehouse, chave primária = sub["id"]
save_last_run(started)
```

* Peça `first: 100` (o máximo) e não aninhe `orders` dentro de `subscriptions`: 100 × 100 ultrapassa o teto de nós por request. Traga os pedidos numa segunda consulta (receita 2).
* `updatedAt` muda com qualquer edição, cobrança, pausa ou cancelamento, então uma assinatura cancelada entra sozinha na próxima execução.
* Com 2000 requests por minuto por chave, uma loja com 50.000 assinaturas sincroniza inteira em menos de um minuto; o ritmo é do seu warehouse, não da API.

***

## 2. Conciliar os pedidos do mês com a contabilidade

**Objetivo:** uma lista de todas as cobranças de assinatura do mês, com o número do pedido na Shopify e o ID do pagamento no Mercado Pago, para cruzar com a liquidação do MP.

```graphql theme={null}
{
  orders(first: 100, filter: { createdAfter: "2026-10-01T00:00:00Z", createdBefore: "2026-11-01T00:00:00Z" }) {
    totalCount
    nodes {
      id type cycleNumber amount currency createdAt
      shopifyOrderId shopifyOrderName mercadoPagoPaymentId
      subscription { id customerEmail }
    }
    pageInfo { hasNextPage endCursor }
  }
}
```

* `type` distingue a primeira cobrança (`FIRST`) das renovações (`RENEWAL`); o desconto de primeiro pedido vale só para a primeira.
* Um pedido combinado (auto-merge) aparece **uma vez por assinatura** com o mesmo `shopifyOrderId`: cada linha é uma cobrança do Mercado Pago, então a soma de `amount` bate com o cobrado mesmo que o pedido da Shopify seja um só.
* Se a sua contabilidade precisa do imposto, cada assinatura traz `tax { amount rate title included }`.

***

## 3. Um bot de suporte que pausa, pula e cancela

**Objetivo:** deixar a sua equipe de atendimento (ou um agente de IA) resolver «me pausa por um mês» ou «cancela a minha assinatura» pela própria ferramenta, com as mesmas regras do portal.

**Como:** busque a assinatura pelo email, execute a mutation com um `Idempotency-Key` e mostre o `userError` como veio, se houver.

```graphql theme={null}
query Find($email: String!) {
  customer(email: $email) {
    subscriptions(first: 10, filter: { status: [ACTIVE, PAUSED] }) {
      nodes { id status nextPaymentDate plan { name } }
    }
  }
}
```

```bash theme={null}
curl https://api.appreval.com/api/graphql \
  -H "Authorization: Bearer reval_XXXXXXXX" \
  -H "Idempotency-Key: ticket-48213-pause" \
  -H "Content-Type: application/json" \
  -d '{"query":"mutation($id: ID!) { subscriptionPause(id: $id) { subscription { id status } userErrors { code message } } }","variables":{"id":"cmg…"}}'
```

* Use como `Idempotency-Key` algo que identifique a intenção (o ID do ticket mais a ação): se a sua ferramenta tentar de novo, a API devolve a mesma resposta sem pausar duas vezes.
* `MIN_CYCLES` quer dizer que o plano tem um compromisso mínimo que o cliente não cumpriu. É a mesma regra que ele vê no portal: decida no seu bot se o agente pode pulá-la com `overrideMinCycles: true` (age como o lojista).
* Para cancelar, peça ao agente um motivo da lista (`TOO_EXPENSIVE`, `DELIVERY_PROBLEMS`…) e passe o texto livre em `note`: ele aparece na análise de cancelamentos do dashboard.
* O histórico da assinatura mostra «API (nome da chave)», então dê à chave o nome da ferramenta («Zendesk bot»).

***

## 3b. Um bot que monta o checkout pronto para o cliente

**Objetivo:** seu bot de vendas (WhatsApp, CRM) fecha a conversa com um link em que o cliente só revisa, informa o cartão e paga — produtos, quantidades e os dados dele já no lugar.

**Como:** monte o link com `checkoutLinkCreate` — uma linha por produto com seu `planId` (liste-os com `plans`) — passando os dados do cliente e a sua referência:

```bash theme={null}
curl https://api.appreval.com/api/graphql \
  -H "Authorization: Bearer reval_XXXXXXXX" \
  -H "Idempotency-Key: conv-9154-link" \
  -H "Content-Type: application/json" \
  -d '{"query":"mutation { checkoutLinkCreate(lines: [{ planId: \"cmg…\", quantity: 2 }, { planId: \"cmh…\", quantity: 1 }], customer: { email: \"ana@exemplo.com\", firstName: \"Ana\", phone: \"+5511944445555\", address: { address1: \"Rua 123\", streetNumber: \"45\", city: \"São Paulo\", province: \"São Paulo\", zip: \"01310-100\", countryCode: \"BR\" } }, externalRef: \"conv-9154\") { url expiresAt userErrors { code message field } } }"}'
```

* A resposta traz a **URL pronta para enviar** e `expiresAt`. A URL não leva nenhum dado pessoal — só um código opaco; os dados vivem no Reval, vencem em 30 dias e são apagados ao serem usados.
* Os planos podem ter **frequências diferentes**: o cliente paga uma vez e cada frequência vira a sua própria assinatura.
* Quando o cliente completa a compra, o seu `externalRef` aparece em `Subscription.externalRef` e como `external_ref` no webhook `subscription.created` — é aí que o bot fecha o ciclo com a conversa dele.
* O link é de **uso único**: para uma nova compra, gere outro. Um link sem `customer` nem `externalRef` também funciona e é permanente.
* Os erros voltam por `userErrors` nomeando a linha (`lines[1]: …`): plano inativo (`PLAN_UNAVAILABLE`), bundle ou produto-caixa (`REJECTED`), input inválido (`INVALID_INPUT`). Um link morto nunca é gerado.

***

## 4. Alertas de cobranças recusadas no Slack (ou onde quiser)

**Objetivo:** saber na hora quando o Mercado Pago recusa uma renovação, com o motivo em palavras, para falar com o cliente antes de a assinatura ser cancelada.

**Como:** um endpoint inscrito só em `subscription.payment_failed`. O payload traz `reason_text` pronto para colar numa mensagem.

```python theme={null}
from flask import Flask, request, abort
import hmac, hashlib, time, requests

app = Flask(__name__)
SECRET = "whsec_XXXXXXXX"
SEEN = set()  # em produção, uma tabela ou Redis

@app.post("/reval")
def reval():
    raw = request.get_data()
    parts = dict(kv.split("=", 1) for kv in request.headers["X-Reval-Signature"].split(","))
    expected = hmac.new(SECRET.encode(), f"{parts['t']}.".encode() + raw, hashlib.sha256).hexdigest()
    if abs(time.time() - int(parts["t"])) > 300 or not hmac.compare_digest(expected, parts["v1"]):
        abort(401)
    delivery_id = request.headers["X-Reval-Delivery"]
    if delivery_id in SEEN:
        return "", 200          # nova tentativa de algo já processado
    SEEN.add(delivery_id)
    event = request.get_json()
    if event["topic"] == "subscription.payment_failed":
        d = event["data"]
        requests.post(SLACK_WEBHOOK, json={"text": f":warning: Cobrança recusada de {d['customer_email']} ({d['amount_formatted']}): {d['reason_text']}"})
    return "", 200
```

* Responda 200 **antes** de fazer qualquer coisa lenta; se o Slack demorar mais de 10 segundos, a Reval conta a tentativa como falha e tenta de novo.
* O Mercado Pago tenta a cobrança de novo por conta própria; se depois aprovar, você recebe `subscription.charged` para a mesma assinatura.
* Para ver o estado atual sem esperar o próximo evento, consulte `subscription(id) { lastPaymentFailedAt lastPaymentFailReason status }`.

***

## 5. Webhooks no Zapier, Make ou n8n, sem escrever código

**Objetivo:** cada cancelamento adicionar uma linha no Google Sheets, ou cada nova assinatura criar um contato no seu CRM.

1. Na sua ferramenta, crie um trigger do tipo **Webhook / Catch hook** e copie a URL que ela dá (é `https`, então a Reval aceita).
2. Em **Reval → Integrações → Webhooks**, adicione um endpoint com essa URL e marque só os eventos de que precisa (por exemplo `subscription.cancelled`).
3. Clique em **Enviar teste**: a sua ferramenta recebe um evento `test` com o mesmo formato de um real, e com ele você mapeia os campos (`data.customer_email`, `data.product_names`, `data.reason`…).
4. Salve o fluxo. A partir daí, cada evento real o dispara.

* Essas ferramentas respondem 2xx na hora, que é o que a Reval espera. Não precisa verificar a assinatura para começar; se quiser, Make e n8n têm passos de HMAC com os quais você replica a [verificação](https://docs.appreval.com/pt/desenvolvedores/webhooks.md#verificar-a-assinatura).
* Use `data.subscription_id` como chave na sua planilha ou CRM para não duplicar linhas quando a Reval tenta uma entrega de novo.

***

## 6. Email de retenção com link direto para o portal

**Objetivo:** mandar a um cliente que cancelou (ou que teve uma cobrança recusada) um email com um botão que abre **a** assinatura dele no portal, já autenticado, para reativar ou trocar o cartão.

```graphql theme={null}
mutation { subscriptionPortalLink(id: "cmg…") { url expiresAt userErrors { code message } } }
```

* O link é assinado e expira: gere-o na hora de enviar o email, não o guarde.
* Trate-o como uma credencial: quem o tiver entra no portal desse cliente. Não o envie por canais onde possa ficar exposto.
* Se você usa o Klaviyo, os eventos que a Reval envia já incluem `portal_subscription_url`, que abre a assinatura no portal com o login normal do cliente; `subscriptionPortalLink` é para quando você precisa do acesso direto a partir de outra ferramenta.

***

## O que escolher: API, webhooks ou Klaviyo

| Você precisa… | Use |
| - | - |
| Uma foto atual dos dados, ou o histórico | API (consultas) |
| Reagir na hora a algo que aconteceu | Webhooks |
| Agir sobre uma assinatura a partir de outra ferramenta | API (ações) |
| Emails automáticos para o cliente final | Klaviyo ou Shopify Flow, que recebem os mesmos eventos sem programar nada |
| O mesmo no Zapier / Make / n8n | Webhooks |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.