Skip to main content
Este guia é para a empresa parceira emissora do Programa de Parceiros Zupy: a empresa que distribui Zparaosusuaˊriosdela(porexemplo,acadalitrodeoˊleodescartado)equepagaessesZ para os usuários dela (por exemplo, a cada litro de óleo descartado) e que paga esses Z com o saldo da própria carteira.
Você integra um PDV, um delivery ou outro sistema de pedidos com a Zupy? Esse é o parceiro de integração, com outra chave e outras rotas: veja Partner Onboarding. As rotas deste guia só aceitam a chave de parceiro emissor.

Antes de começar

Você precisa de três coisas, todas configuradas com a equipe da Zupy:
  1. Perfil de parceiro ativo, com o recurso Emissor. Sem isso, toda chamada responde 403 com error_code partner-not-active.
  2. Saldo de Znacarteiradaempresaparceira.∗∗Cadacreˊditoecadatransfere^nciasaidessesaldo.Osaldoaparecenopainelem∗∗Integrac\co~es→ParceiroZupy∗∗eoextratoem∗∗CarteiraZ na carteira da empresa parceira.** Cada crédito e cada transferência sai desse saldo. O saldo aparece no painel em **Integrações → Parceiro Zupy** e o extrato em **Carteira Z. A API não expõe o saldo.
  3. Pelo menos uma campanha, para creditar. A campanha define a ação (por exemplo, “Descarte correto de óleo”), a unidade (litro), o valor de cada unidade em Z$, a vigência e os limites. A equipe da Zupy cria a campanha e informa o campaign_id dela. As suas campanhas, com o orçamento consumido, aparecem no painel em Integrações → Parceiro Zupy → Campanhas.
URL base: https://api.zupy.com

1. Gerar a chave de API

1

Abra o painel do parceiro

Entre no painel da Zupy como administrador da empresa parceira e abra Integrações → Parceiro Zupy (/integrations/partner). No cartão Chaves de parceiro, clique em Gerenciar chaves.
2

Crie a chave com as permissões certas

Clique em Nova chave de API, dê um nome (por exemplo, “ERP”) e marque só as permissões de que a integração precisa:Não existe permissão curinga: partner.* não libera nada.
3

Guarde a chave na hora

A chave completa aparece uma única vez. Guarde-a no seu cofre de segredos. Se ela vazar ou se perder, revogue no mesmo lugar e crie outra: a revogação vale na hora.
A chave de parceiro emissor só alcança as rotas /api/v2/partner/*. Qualquer outra rota da API v2 responde 403 com code partner-key-outside-partner-namespace.

2. Autenticar

Envie a chave no cabeçalho X-API-Key em toda chamada. A empresa parceira é sempre a dona da chave: nenhuma rota aceita um id de parceiro no corpo, na query ou em outro cabeçalho.

3. Ler o seu perfil

GET /api/v2/partner/profile/ (permissão partner.read) devolve os recursos ligados e as condições comerciais. As taxas são frações: 0.2000 é 20%.
O perfil nunca traz PIX, CNPJ ou contatos.

4. Creditar Z$ a um usuário

POST /api/v2/partner/credits/ (permissão partner.credit) credita quantity unidades da ação de uma campanha sua ao usuário que os identificadores apontam. O valor em Z$ é quantity × unit_value da campanha.
Resposta 201 (crédito novo):

Como o usuário é identificado

  • A Zupy procura o usuário pelos identificadores enviados. Se ninguém for encontrado, cria a conta, uma vez só: o mesmo telefone ou e-mail nunca vira duas contas.
  • Para criar a conta é preciso phone ou email. Com só cpf ou wallet_address de alguém que ainda não tem conta, a resposta é 422 customer-not-identified.
  • Se dois identificadores apontarem para pessoas diferentes, nada é creditado: 409 partner-identity-conflict, com identifiers listando só os tipos que discordam (phone, email, cpf, wallet), nunca os valores.
  • Identificador malformado (telefone inválido, CPF com dígito errado, carteira Solana inválida, e-mail inválido) é recusado antes de qualquer busca, com 422 validation-error apontando o campo.

Idempotência: external_reference

Reenviar o mesmo external_reference com os mesmos dados é seguro: a resposta é 200 com o mesmo crédito, e nada é creditado de novo. Use isso para repetir uma chamada que expirou ou caiu sem resposta. Crédito e transferência têm espaços de referência separados: a mesma external_reference num crédito e numa transferência são operações diferentes.

Como o crédito aparece para o usuário

No extrato do usuário, o crédito mostra a origem e o motivo a partir do nome da empresa parceira e da ação da campanha, por exemplo: Z$ 200 via Biord: Descarte correto de óleo (1 litro). O crédito não tem campo de motivo livre; o motivo é a ação da campanha.

Limites da campanha

A campanha pode ter orçamento total e teto por usuário. Um crédito que ultrapassaria um deles é recusado com 422 credit-limit-exceeded, que traz limit (budget_total ou per_user), limit_value, used e requested. Uma campanha pausada, encerrada ou fora da vigência responde 422 campaign-not-creditable.

status: o registro on-chain

5. Consultar créditos

  • GET /api/v2/partner/credits/ (permissão partner.read): os seus créditos, do mais novo para o mais antigo, paginados por cursor. Siga os links next e previous. page_size vai de 1 a 100 (padrão 25). Filtre pela sua referência com ?external_reference= (busca exata).
  • GET /api/v2/partner/credits/{credit_id}/ (permissão partner.read): um crédito seu, com o status atual. O crédito de outro parceiro responde 404, igual a um que não existe.

6. Transferir Z$ a um usuário ou a uma empresa

POST /api/v2/partner/transfers/ (permissão partner.transfer) envia amount Z$ da carteira da empresa parceira para exatamente um destino, sem campanha:
  • um usuário, pelos identificadores (phone, email, cpf, wallet_address), com as mesmas regras de identificação do crédito; ou
  • uma empresa, por company_id. Uma filial é creditada na carteira da matriz: a resposta traz destination_company_id (a empresa pedida) e credited_company_id (a que recebeu). Você credita a empresa, mas nunca lê o saldo dela.
Enviar identificadores de pessoa e company_id juntos, ou nenhum dos dois, responde 422 validation-error.
Resposta 201, para um usuário:
Para uma empresa, customer_id vem null e os dois ids de empresa vêm preenchidos.
  • Limites de transferência: a empresa parceira pode ter tetos por destinatário e no total, por dia, semana ou mês. Uma transferência que ultrapassaria um deles responde 422 transfer-limit-exceeded, com limit, limit_value, used e requested.
  • status: os mesmos valores do crédito. Para uma empresa, o registro on-chain tem duas etapas e só fica settled quando as duas terminam. Nesse caso, settlement_failed não é final enquanto a Zupy reenvia a última etapa: o status volta a settling e depois vira settled.
  • Não há GET de transferências. Para saber o status atual, reenvie o mesmo POST com a mesma external_reference e os mesmos dados: a resposta 200 traz a transferência com o status de agora, sem transferir de novo.

7. Saldo

A API do parceiro não expõe o saldo. Acompanhe-o no painel (Integrações → Parceiro Zupy e Carteira Z$). Quando o saldo não cobre a operação, nada é movimentado:
  • 402 insufficient-balance: o saldo de Z$ da carteira não cobre o crédito ou a transferência;
  • 402 partner-not-funded-on-chain: a carteira ainda não recebeu Z$ confirmados on-chain (aguarde a confirmação do pacote comprado).

8. Webhooks

A Zupy avisa o seu sistema quando um crédito ou uma transferência é criado. O aviso sai depois que a operação é gravada, nunca dentro da requisição, e nunca para um reenvio idêntico (200).

Configurar

No painel, em Integrações → Parceiro Zupy → Webhook (/integrations/webhooks):
  1. Informe a URL do seu endpoint (https://, pública). A Zupy recusa entregar para uma URL que resolva para um endereço interno ou privado.
  2. Escolha os eventos. Sem nenhum evento marcado, você recebe todos.
  3. Guarde o segredo de assinatura (whsec_…): ele aparece completo só ao ser gerado ou rotacionado.
  4. Use Enviar teste para receber um evento ping assinado.

O que chega

POST para a sua URL, com Content-Type: application/json e estes cabeçalhos:
O data de partner.transfer.created traz transfer_id, external_reference, amount, reason, customer_id, destination_company_id, credited_company_id e created_at, e o event_id é transfer:<transfer_id>:created. Nenhum evento traz dado pessoal (telefone, e-mail, CPF ou carteira), saldo ou status on-chain: o status muda depois do evento; leia-o em GET /api/v2/partner/credits/{credit_id}/.

Verificar a assinatura

Calcule o HMAC-SHA256 sobre os bytes recebidos, sem reformatar o JSON, e compare em tempo constante.

Tentativas e duplicidade

  • Responda 2xx rápido (o limite é 10 segundos) e processe depois.
  • Sem 2xx, a Zupy tenta de novo, com espera crescente, até 4 tentativas no total. Toda tentativa reenvia o mesmo corpo, com o mesmo event_id e o mesmo X-Webhook-Id.
  • Deduplique pelo event_id: o mesmo evento nunca é gerado duas vezes, mas a mesma entrega pode chegar mais de uma vez.

9. Erros

Todo erro vem em JSON no formato RFC 7807 (Problem Details). As recusas de parceiro trazem também um error_code estável, e o type termina nele. Trate pelo error_code: o detail é texto para pessoas e pode mudar.
Erro de validação do corpo (campo faltando, tipo errado, identificador malformado) é 422 validation-error, com errors apontando cada campo:

Chave e acesso

Créditos e transferências

Em toda recusa, nada é gravado: nenhum Z$ sai da carteira.

10. Limites de requisição

600 requisições por minuto por empresa parceira, somando todas as chaves dela. Acima disso, 429: espere e tente de novo. Criar mais chaves não aumenta o limite.

Próximos passos