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/v1Permissões:
purchases:write: Lançar e estornar comprascustomers:read: Consultar cliente e saldorewards:read: Listar prêmiosredemptions: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 carteirapoints.earned: Pontos ganhos numa comprapurchase.reversed: Compra estornadareward.redeemed: Prêmio ou desconto trocadomission.completed: Desafio concluídoorder.created: Pedido feito pelo celularorder.updated: Pedido mudou de etapalevel.up: Cliente subiu de nívelbadge.earned: Cliente ganhou uma conquistacheckin.created: Check-in na lojacoupon.used: Cupom usadoraffle.entered: Cliente entrou num sorteioraffle.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." } }401chave ausente ou inválida ·403sem permissão ou fora do plano ·402conta suspensa422regra do programa (saldo, prêmio sem estoque, telefone inválido…) ·404não encontrado429mais de 600 chamadas por minuto na mesma chave
Dúvidas de integração? Fale com o time.