API do AmoPonto

Lance compras do seu PDV ou e-commerce, consulte saldo, crie trocas e receba avisos assinados. Versão 1.

Sumário: Autenticação · Compras · Clientes · Prêmios e trocas · Sorteios · Webhooks · SDK · Widgets · Erros e limites

Autenticação

Crie a chave em Painel → Integrações (plano Região) e escolha só as permissões necessárias. Envie em todas as chamadas:

Authorization: Bearer amo_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
Base: https://amoponto.com.br/api/v1

Permissões:

  • purchases:write: Lançar e estornar compras
  • customers:read: Consultar cliente e saldo
  • rewards:read: Listar prêmios
  • redemptions:write: Criar e acompanhar trocas

GET/me · loja, permissões e regras do programa

Compras (geram pontos)

POST/events · purchases:write

Telefone ainda sem cadastro? Os pontos ficam guardados e o cliente recebe ao ativar a carteira. A mesma idempotency_key nunca credita duas vezes (pode repetir a chamada com segurança).

POST /api/v1/events
{
  "type": "purchase",
  "customer": { "phone": "11999887766" },
  "data": { "amount_cents": 8990, "external_ref": "NF-2026-00123", "occurred_at": "2026-10-07T19:30:00-03:00" },
  "idempotency_key": "pedido-2026-00123"
}

201 { "id": "…", "duplicate": false, "points": 449, "balance": 1689, "customer": { … }, "missions_completed": [] }

POST /purchases é o mesmo endpoint. external_ref (nº do pedido ou da nota) também impede lançar duas vezes a mesma venda; occurred_at aceita até 7 dias para trás.

POST/purchases/{id}/reverse · purchases:write

{ "reason": "Pedido cancelado" }   →   200 { "id": "…", "reversed": true, "points_removed": 449 }

Clientes

GET/customers?phone=11999887766 · customers:read

GET/customers/{id} · customers:read

200 {
  "id": "…", "phone": "+5511999887766", "name": "Maria Souza", "activated": true, "visits": 12, "total_spent_cents": 98400,
  "xp": 2140, "level": "Ouro",
  "balance": { "available": 1689, "pending": 0, "reserved": 0, "usable": 1689, "expiring_soon": 120 }
}

Prêmios e trocas

GET/rewards · rewards:read

POST/redemptions · redemptions:write

A troca fica reservada por 5 minutos e só vale quando o cliente confirma no celular (o pedido aparece na tela inicial do app da loja). Ninguém gasta pontos de outra pessoa.

{ "customer": { "phone": "11999887766" }, "reward_id": "…" }
{ "customer": { "phone": "11999887766" }, "discount_points": 500, "bill_cents": 8000 }

201 { "id": "…", "status": "reserved", "points": 500, "discount_cents": 500, "expires_at": "…" }

GET/redemptions/{id} · redemptions:write

status: reserved → confirmed (pode entregar) ou expired/cancelled.

Sorteios

GET/raffles · customers:read

Sorteios abertos e realizados, com número de participantes e ganhadores (nome curto). Para reagir em tempo real, use os eventos raffle.entered e raffle.drawn.

Webhooks

Cadastre o endereço (https) em Painel → Integrações. Enviamos POST com JSON e estes cabeçalhos: X-AmoPonto-Event, X-AmoPonto-Delivery e X-AmoPonto-Signature: t=<unix>,v1=<hex>, onde v1 = HMAC-SHA256(segredo, "<t>.<corpo cru>"). Responda 2xx em até 10 s; se não, tentamos de novo (1, 5, 15, 60, 180, 720 e 1440 min).

  • customer.activated: Cliente ativou a carteira
  • points.earned: Pontos ganhos numa compra
  • purchase.reversed: Compra estornada
  • reward.redeemed: Prêmio ou desconto trocado
  • mission.completed: Desafio concluído
  • order.created: Pedido feito pelo celular
  • order.updated: Pedido mudou de etapa
  • level.up: Cliente subiu de nível
  • badge.earned: Cliente ganhou uma conquista
  • checkin.created: Check-in na loja
  • coupon.used: Cupom usado
  • raffle.entered: Cliente entrou num sorteio
  • raffle.drawn: Sorteio realizado
{ "id": "evt_…", "type": "points.earned", "created_at": "2026-10-07T22:31:04.120Z",
  "data": { "customer": { "id": "…", "phone": "+5511999887766", "activated": true },
            "purchase": { "id": "…", "amount_cents": 8990, "external_ref": "NF-2026-00123" },
            "points": 449, "balance": { "available": 1689, "pending": 0 }, "first_purchase": false } }

Conferindo a assinatura em Node:

import { createHmac, timingSafeEqual } from "node:crypto";
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
const ok = Math.abs(Date.now() / 1000 - Number(t)) < 300 &&
  timingSafeEqual(Buffer.from(createHmac("sha256", SECRET).update(t + "." + rawBody).digest("hex")), Buffer.from(v1));

SDK (Node 18+)

import { AmoPonto, verifyWebhook } from "@amoponto/sdk";
const amo = new AmoPonto({ apiKey: process.env.AMOPONTO_KEY });

await amo.purchase({ phone: "11999887766", amountCents: 8990, idempotencyKey: "pedido-123" });
const cliente = await amo.customer("11999887766");          // null se ainda não comprou
const troca = await amo.redeem({ phone: "11999887766", rewardId: "…" });
const final = await amo.waitRedemption(troca.id);            // espera o cliente confirmar

if (!verifyWebhook(rawBody, req.headers["x-amoponto-signature"], process.env.AMOPONTO_WHSEC)) return res.sendStatus(400);

Widgets

Valem para todos os planos e não expõem dado de cliente:

<script src="https://amoponto.com.br/widgets.js" defer></script>
<amoponto-saldo loja="seu-slug"></amoponto-saldo>
<amoponto-vitrine loja="seu-slug" limite="6"></amoponto-vitrine>

Erros e limites

{ "error": { "code": "insufficient", "message": "Saldo insuficiente." } }
  • 401 chave ausente ou inválida · 403 sem permissão ou fora do plano · 402 conta suspensa
  • 422 regra do programa (saldo, prêmio sem estoque, telefone inválido…) · 404 não encontrado
  • 429 mais de 600 chamadas por minuto na mesma chave

Dúvidas de integração? Fale com o time.