Referência do API

Referência completa do endpoint com exemplos TypeScript. Todos os exemplos usam o handypay ajudante da Início Rápido. O Tooling pode baixar o Contrato OpenAPI 3.1.

URL Base

https://api.handypay.me/api/v1
bash

Versão do API: 2025-01-01 (retorno em X-API-Version cabeçalho em cada resposta).

Autenticação

Todos os pedidos requerem um token do portador na Authorization Cabeçalho.

Authorization: Bearer hp_live_your_api_key_here
javascript
  • Chaves prefixadas com hp_live_ são para produção.
  • Chaves prefixadas com hp_test_ são para o sandbox/testing.
  • Gerar e gerenciar chaves a partir do Portal do lojista.

O modo de teste está totalmente isolado

Cada hp_test_ A requisição utiliza uma conta de teste dedicada. Produtos de teste, clientes, pagamentos, assinaturas e terminais webhook nunca aparecem em atividades de negócios ao vivo. Veja-os no Espaço de trabalho do modo de teste.

Lista de pagamentos de testes
curl https://api.handypay.me/api/v1/test-payments \
  -H "Authorization: Bearer hp_test_your_api_key_here"
bash

Limites de Taxa

1.000 pedidos por hora por chave do API. Excedendo o limite retorna 429 com uma Retry-After Cabeçalho.

Formato de resposta

Cada resposta é envolto num envelope padrão.

Sucesso

JSON
{
  "success": true,
  "data": { ... },
  "request_id": "550e8400-e29b-41d4-a716-446655440000"
}
json

Erro

JSON
{
  "success": false,
  "error": {
    "code": "validation_error",
    "message": "Name is required"
  },
  "request_id": "550e8400-e29b-41d4-a716-446655440000"
}
json

Paginação

Todos os parâmetros de lista usam paginação baseada em cursores.

CampoTipoRequeridoDesignação das mercadorias
limitnumberNãoItens por página (1–100, padrão 10)
starting_afterstringNãoID do último item da página anterior

Resposta inclui has_more: true quando existem páginas adicionais.

Produtos

Crie e gerencie produtos para compras únicas.

MétodoLocalizaçãoDesignação das mercadorias
POST/v1/productsCriar um produto
GET/v1/productsProdutos da lista
GET/v1/products/:idObter um produto
PUT/v1/products/:idActualizar um produto
DELETE/v1/products/:idArquivar um produto

Criar um produto

TypeScript
const product = await handypay("/products", {
  method: "POST",
  body: JSON.stringify({
    name: "Premium Plan",
    description: "Access to all features",
    price: {
      amount: 2999,
      currency: "usd",
    },
  }),
});

console.log(product.id); // "prod_abc123"
typescript
exemplo cURL
cURL
curl -X POST https://api.handypay.me/api/v1/products \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Premium Plan",
    "description": "Access to all features",
    "price": {
      "amount": 2999,
      "currency": "usd"
    }
  }'
bash

Organismo de pedido

CampoTipoRequeridoDesignação das mercadorias
namestringSim.Denominação do produto
descriptionstringNãoDescrição do produto
imagesstring[]NãoAté 8 URLs de imagens
metadataobjectNãoPares de valor de chave personalizados (até 50 teclas)
activebooleanNãoVerdadeiro padrão
urlstringNãoPágina do produto URL em seu site
shippablebooleanNãoSe o produto requer ou não envio
unit_labelstringNãoRótulo por unidade (por exemplo: "sede", "licença")
statement_descriptorstringNãoTexto do extrato bancário (máximo de 22 caracteres)
tax_codestringNãoCódigo fiscal do Stripe
price.amountnumberNãoPreço na unidade monetária mais pequena (cents)
price.currencystringNãoCódigo monetário do ISO 4217 (por exemplo: "usd", "jmd")
price.tax_behaviorstringNãoInclusivo, exclusivo ou não especificado

Clientes

Gerencie registros de clientes para compras e assinaturas repetidas.

MétodoLocalizaçãoDesignação das mercadorias
POST/v1/customersCriar um cliente
GET/v1/customersListar clientes
GET/v1/customers/:idObter um cliente
PUT/v1/customers/:idAtualizar um cliente
DELETE/v1/customers/:idApagar um cliente

Criar um cliente

TypeScript
const customer = await handypay("/customers", {
  method: "POST",
  body: JSON.stringify({
    email: "customer@example.com",
    name: "Jane Doe",
  }),
});

console.log(customer.id); // "cus_abc123"
typescript
exemplo cURL
cURL
curl -X POST https://api.handypay.me/api/v1/customers \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "customer@example.com",
    "name": "Jane Doe"
  }'
bash

Organismo de pedido

CampoTipoRequeridoDesignação das mercadorias
emailstringSim.E- mail do cliente
namestringNãoNome do cliente
phonestringNãoNúmero de telefone do cliente
metadataobjectNãoPares de valor de chave personalizados

Sessões de pagamento

Crie sessões de checkout hospedadas para pagamentos únicos. Os preços padrão do HandyPay se aplicam: 4,9% + US$0.40 por transação no plano gratuito, ou 4,2% + US$0.40 no Pro. Não há taxa extra no API ou plataforma no topo.

MétodoLocalizaçãoDesignação das mercadorias
POST/v1/payment-sessionsCriar uma sessão de pagamento
GET/v1/payment-sessions/:idObter o estado da sessão
GET/v1/test-paymentsPagamentos de teste da lista (apenas para hp_test_)

Criar uma sessão de pagamento (com o preço existente)

TypeScript
const session = await handypay("/payment-sessions", {
  method: "POST",
  body: JSON.stringify({
    line_items: [{ price_id: "price_abc123", quantity: 1 }],
    success_url: "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
    cancel_url: "https://yoursite.com/cancel",
  }),
});

// Redirect customer to checkout
window.location.href = session.url;
typescript
exemplo cURL
cURL
curl -X POST https://api.handypay.me/api/v1/payment-sessions \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "line_items": [{ "price_id": "price_abc123", "quantity": 1 }],
    "success_url": "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://yoursite.com/cancel"
  }'
bash

Criar uma sessão de pagamento (quantidade personalizada)

TypeScript
const session = await handypay("/payment-sessions", {
  method: "POST",
  body: JSON.stringify({
    line_items: [{
      amount: 5000,
      currency: "usd",
      name: "Custom Order",
      quantity: 1,
    }],
    success_url: "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
    cancel_url: "https://yoursite.com/cancel",
  }),
});
typescript
exemplo cURL
cURL
curl -X POST https://api.handypay.me/api/v1/payment-sessions \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "line_items": [{
      "amount": 5000,
      "currency": "usd",
      "name": "Custom Order",
      "quantity": 1
    }],
    "success_url": "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://yoursite.com/cancel"
  }'
bash

Organismo de pedido

CampoTipoRequeridoDesignação das mercadorias
line_itemsarraySim.Pelo menos uma linha de item
success_urlstringSim.Redirecionar o URL após o pagamento bem-sucedido
cancel_urlstringSim.Redirecionar o URL se o cliente cancelar
customer_idstringNãoCliente existente ID
customer_emailstringNãoPré-preencher o e-mail (se não houver customer_id)
pass_fees_to_customerbooleanNãoAdicionar taxas de processamento e serviço ao total do cliente
metadataobjectNãoPares de valor de chave personalizados
collect_shipping_addressbooleanNãoRecolha um endereço de envio
billing_address_collectionstringNãoauto ou necessário
shipping_countriesstring[]NãoPaíses de destino permitidos do ISO-2
shipping_optionsarrayNãoSelos e montantes de envio na unidade monetária mais pequena

Campos de itens de linha

CampoTipoRequeridoDesignação das mercadorias
price_idstringNãoStripe Price ID existente
amountnumberNãoQuantidade personalizada em cêntimos
currencystringNãoObrigatório com montante
namestringNãoObrigatório com montante
quantitynumberSim.Quantidade

Fornecer qualquer um dos price_id ou amount+currency+name por item de linha.

Substituições do HandyPay {CHECKOUT_SESSION_ID} no URL de sucesso. Consulte que ID com o mesmo comerciante e o mesmo modo de chave live/test que o criou. A sessão continua questionável após a conclusão, mas seu webhook assinado ainda deve conduzir o cumprimento da fatura.

Pagamentos Incorporados

Crie um PaymentIntent no seu servidor quando quiser renderizar o Stripe Elements na sua própria página de checkout. A chave da API HandyPay permanece no servidor; envie ao navegador apenas a chave publicável retornada, o ID da conta conectada e o segredo de cliente de curta duração.

MétodoLocalizaçãoDesignação das mercadorias
POST/v1/payment-intentsCriar um PaymentIntent integrado
TypeScript no servidor
const intent = await handypay("/payment-intents", {
  method: "POST",
  body: JSON.stringify({
    amount: 5000,
    currency: "ttd",
    description: "Order #1042",
    customer_email: "buyer@example.com",
    pass_fees_to_customer: true,
    metadata: { order_id: "1042" },
  }),
});

// Pass these values to Stripe.js/Elements. Never pass HANDYPAY_API_KEY.
return {
  clientSecret: intent.client_secret,
  publishableKey: intent.publishable_key,
  stripeAccount: intent.stripe_account,
};
typescript
CampoTipoRequeridoDesignação das mercadorias
amountnumberSim.Número inteiro positivo na unidade monetária mais pequena
currencystringSim.ISO 4217 código de moeda de três letras
descriptionstringNãoDesignação do pagamento
customer_emailstringNãoEmail do cliente para recibos e reconciliação
pass_fees_to_customerbooleanNãoReembolso do valor para que o cliente cubra as taxas
metadataobjectNãoIdentificadores de sua encomenda ou fatura

Montante das restituições

Reembolso de um pagamento de propriedade do comerciante autenticado. O HandyPay verifica a propriedade do pagamento e o saldo disponível antes de criar a inversão. Omitir amount para um reembolso total.

MétodoLocalizaçãoDesignação das mercadorias
POST/v1/refundsCriar um reembolso total ou parcial
TypeScript
const refund = await handypay("/refunds", {
  method: "POST",
  body: JSON.stringify({
    session_id: "cs_live_...",
    amount: 2500,
    reason: "requested_by_customer",
  }),
});

console.log(refund.id, refund.status);
typescript
CampoTipoRequeridoDesignação das mercadorias
session_idstringCondicionalID da Checkout Session (cs_...). Use este campo ou payment_intent
payment_intentstringCondicionalID do PaymentIntent (pi_...). Use este campo ou session_id
amountnumberNãoMontante parcial da restituição na unidade monetária mais pequena
reasonstringNãoduplicate, fraudulent ou requested_by_customer

Um pagamento contestado, totalmente reembolsado, de troca cruzada ou insuficiente é rejeitado sem criar um reembolso.

Litígios

Reveja os encargos para o comerciante conectado e fornecer evidências antes da data de vencimento.

MétodoLocalizaçãoDesignação das mercadorias
GET/v1/disputesListar litígios
GET/v1/disputes/:idObter uma disputa e seu status de evidência
POST/v1/disputes/:id/evidenceActualizar ou apresentar elementos de prova
TypeScript
const dispute = await handypay("/disputes/dp_123/evidence", {
  method: "POST",
  body: JSON.stringify({
    evidence: {
      customer_email_address: "buyer@example.com",
      product_description: "Annual software subscription",
      customer_communication: "https://files.example.com/evidence/1042.pdf",
    },
    submit: false,
  }),
});
typescript
CampoTipoRequeridoDesignação das mercadorias
evidenceobjectNãoStripe disputa campos de evidência como valores de string
submitbooleanNãoDefinir o valor verdadeiro apenas quando a evidência estiver completa e pronta para revisão

Assinaturas

Crie produtos recorrentes e gerencie assinaturas.

Produtos de assinatura

MétodoLocalizaçãoDesignação das mercadorias
POST/v1/subscription-productsCriar um produto de subscrição
GET/v1/subscription-productsListar produtos de assinatura

Sessões de Assinatura & Gestão

MétodoLocalizaçãoDesignação das mercadorias
POST/v1/subscription-sessionsCriar a verificação da subscrição
GET/v1/subscriptionsListar as assinaturas activas
PATCH/v1/subscriptions/:id/quantityMudar de assento com pronunciação explícita
POST/v1/subscriptions/:id/cancelCancelar no final do período de faturamento

Intervalos de faturamento suportados

weeklybi-weeklymonthlybi-monthlyquarterlysemi-annualannual

Criar um produto de subscrição

TypeScript
const subProduct = await handypay("/subscription-products", {
  method: "POST",
  body: JSON.stringify({
    name: "Pro Plan",
    description: "Monthly pro access",
    amount: 1999,
    currency: "usd",
    interval: "monthly",
    trial_period_days: 14,
  }),
});

console.log(subProduct.price.id); // Use this price_id for subscription sessions
typescript
exemplo cURL
cURL
curl -X POST https://api.handypay.me/api/v1/subscription-products \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pro Plan",
    "description": "Monthly pro access",
    "amount": 1999,
    "currency": "usd",
    "interval": "monthly",
    "trial_period_days": 14
  }'
bash

Organismo de pedido

CampoTipoRequeridoDesignação das mercadorias
namestringSim.Denominação do produto
descriptionstringNãoDescrição do produto
amountnumberSim.Preço na unidade monetária mais pequena
currencystringSim.ISO 4217 código monetário
intervalstringSim.Um dos intervalos suportados
trial_period_daysnumberNãoDuração da experiência gratuita em dias
metadataobjectNãoPares de valor de chave personalizados

Criar checkout com vários assentos

TypeScript
const session = await handypay("/subscription-sessions", {
  method: "POST",
  body: JSON.stringify({
    price_id: subProduct.price.id,
    quantity: 5,
    customer_email: "buyer@example.com",
    success_url: "https://example.com/success",
    cancel_url: "https://example.com/plans",
  }),
});
typescript

Mudar de lugar numa subscrição activa

Quantidade deve ser um inteiro de 1 a 1.000. Escolha como o ajuste de faturamento é tratado em vez de confiar em um padrão implícito.

TypeScript
const updated = await handypay("/subscriptions/sub_123/quantity", {
  method: "PATCH",
  body: JSON.stringify({
    quantity: 8,
    proration_behavior: "create_prorations",
  }),
});
typescript
CampoTipoRequeridoDesignação das mercadorias
quantitynumberSim.Nova contagem de assentos de 1 a 1.000
proration_behaviorstringNãocreate_prorations, always_invoice ou none
item_idstringNãoRubrica específica da subscrição quando uma subscrição tem vários produtos

Webhooks

Receba notificações de eventos em tempo real através do HTTP POST para seus objetivos.

MétodoLocalizaçãoDesignação das mercadorias
POST/v1/webhook-endpointsRegistar um ponto final
GET/v1/webhook-endpointsEndpoints da lista
DELETE/v1/webhook-endpoints/:idDesactivar um ponto final
  • Os pontos de extremidade devem usar o HTTPS.
  • Endpoints Webhook registrados com um hp_test_ A chave recebe apenas os eventos de teste. Os destinos vivos e de ensaio são armazenados separadamente.
  • Cada endpoint ativo subscrito a um evento recebe uma entrega independente assinada com o segredo próprio desse endpoint. Não reutilize o segredo de um ponto final para outro.
  • Retorne uma resposta de 2xx em 10 segundos. Armazenar cada evento id antes de efeitos secundários, por isso, as entregas duplicadas são seguras.
  • Após 10 falhas consecutivas de entrega, um ponto final é automaticamente desativado.

Tipos de eventos suportados

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • checkout.session.completed
  • checkout.session.expired
  • checkout.session.async_payment_succeeded
  • checkout.session.async_payment_failed
  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • charge.refunded
  • charge.dispute.created
  • charge.dispute.closed
Trate webhooks assinados como a fonte de pagamento da verdade – não o redirecionamento do sucesso do navegador. A payment_intent.payment_failed o evento identifica o PaymentIntent em data.id; use seus metadados (por exemplo order_id) para conciliá-lo. Utilização checkout.session.expired para a expiração da sessão e os eventos assinc para os métodos de pagamento atrasados.

Verificando assinaturas do webhook

Cada entrega inclui uma X-HandyPay-Signature cabeçalho no formato sha256={hex}. Verifique computando o HMAC-SHA256 do corpo de solicitação bruto usando o segredo de assinatura do seu endpoint:

TypeScript
import { createHmac, timingSafeEqual } from "node:crypto";

function verifySignature(
  payload: string | Buffer,
  secret: string,
  signature: string
): boolean {
  const prefix = "sha256=";
  if (!signature.startsWith(prefix)) return false;
  const hex = signature.slice(prefix.length);
  if (!/^[0-9a-f]{64}$/i.test(hex)) return false;

  const expected = createHmac("sha256", secret).update(payload).digest();
  const received = Buffer.from(hex, "hex");
  return received.length === expected.length && timingSafeEqual(received, expected);
}
typescript

Manipulador de rota de API do Next.js

app/api/webhooks/handypay/route.ts
import { NextRequest, NextResponse } from "next/server";
import { createHmac, timingSafeEqual } from "node:crypto";

export const runtime = "nodejs";
const SECRET = process.env.HANDYPAY_WEBHOOK_SECRET;
if (!SECRET) throw new Error("HANDYPAY_WEBHOOK_SECRET is not configured");

function hasValidSignature(payload: string, signature: string): boolean {
  const prefix = "sha256=";
  if (!signature.startsWith(prefix)) return false;
  const hex = signature.slice(prefix.length);
  if (!/^[0-9a-f]{64}$/i.test(hex)) return false;
  const expected = createHmac("sha256", SECRET).update(payload).digest();
  const received = Buffer.from(hex, "hex");
  return received.length === expected.length && timingSafeEqual(received, expected);
}

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get("x-handypay-signature") ?? "";
  if (!hasValidSignature(body, signature)) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
  }

  let event: { id: string; type: string; data: unknown };
  try {
    event = JSON.parse(body);
  } catch {
    return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
  }

  // Persist event.id before side effects so redeliveries can be ignored safely.
  switch (event.type) {
    case "checkout.session.completed":
      // Fulfill an immediate payment.
      break;
    case "checkout.session.async_payment_succeeded":
      // Fulfill a delayed payment.
      break;
    case "charge.refunded":
      // Mark the matching order as refunded.
      break;
  }

  return NextResponse.json({ received: true });
}
typescript
Manipulador Express.js
routes/webhooks.ts
// Register this route BEFORE app.use(express.json()).
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const router = express.Router();
const SECRET = process.env.HANDYPAY_WEBHOOK_SECRET;
if (!SECRET) throw new Error("HANDYPAY_WEBHOOK_SECRET is not configured");

function hasValidSignature(payload: Buffer, signature: string): boolean {
  const prefix = "sha256=";
  if (!signature.startsWith(prefix)) return false;
  const hex = signature.slice(prefix.length);
  if (!/^[0-9a-f]{64}$/i.test(hex)) return false;
  const expected = createHmac("sha256", SECRET).update(payload).digest();
  const received = Buffer.from(hex, "hex");
  return received.length === expected.length && timingSafeEqual(received, expected);
}

router.post(
  "/handypay",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = String(req.headers["x-handypay-signature"] ?? "");
    if (!hasValidSignature(req.body, signature)) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    const event = JSON.parse(req.body.toString("utf8"));
    // Persist event.id before side effects so redeliveries are idempotent.
    console.log("Verified HandyPay event", event.id, event.type);
    return res.json({ received: true });
  }
);

export default router;
typescript

Formato de carga útil Webhook

JSON
{
  "id": "evt_abc123",
  "type": "payment_intent.succeeded",
  "created": 1706745600,
  "data": { ... }
}
json

Conta

Leia a carga e disponibilidade de pagamento do comerciante conectado, além da conta bancária de pagamento padrão. Os números de banco e roteamento são mascarados; apenas seus últimos quatro dígitos são devolvidos.

MétodoLocalizaçãoDesignação das mercadorias
GET/v1/accountObter conta conectada e detalhes de pagamento mascarados
TypeScript
const account = await handypay("/account");

console.log({
  chargesEnabled: account.chargesEnabled,
  payoutsEnabled: account.payoutsEnabled,
  bank: account.bankAccount?.bankName,
  last4: account.bankAccount?.last4,
});
typescript

Códigos de Erros

CódigoHTTPDesignação das mercadorias
unauthorized401Chave do API em falta ou inválida
key_revoked401Chave API foi revogada
key_expired401A chave do API expirou
rate_limit_exceeded429Muitos pedidos
validation_error400A validação do corpo da solicitação falhou
invalid_url400success_url ou cancel_url inválido
invalid_interval400Intervalo de faturamento não suportado
product_not_found404O produto não existe
customer_not_found404O cliente não existe
session_not_found404A sessão de saída não existe
payment_not_found404O pagamento está faltando ou não é propriedade deste comerciante
subscription_not_found404A assinatura não existe
refund_not_allowed400O pagamento é contestado e não pode ser reembolsado
already_refunded400O pagamento já está totalmente reembolsado
refund_amount_too_large400Montante superior ao saldo remanescente reembolsável
insufficient_balance400O saldo disponível não pode cobrir a restituição
balance_verification_failed400Não foi possível verificar a propriedade ou disponibilidade de saldos
endpoint_not_found404Endpoint do API desconhecido
stripe_error502Stripe API retornou um erro
internal_error500Erro inesperado no servidor
payload_too_large413O organismo de solicitação excede 1MB
prohibited_content400O conteúdo viola a política de uso aceitável
webhook_url_must_be_https400Webhook URL deve usar HTTPS
key_creation_rate_exceeded429Muitas chaves criadas na janela de tempo
max_keys_reached400Teclas API máximas ativas alcançadas (25)

Segurança

  • Manter hp_live_ e hp_test_ chaves no seu servidor. Nunca coloque-os no navegador JavaScript, aplicativos móveis, URLs, logs ou controle de fonte.
  • Todas as respostas incluem X-Content-Type-Options: nosniff, X-Frame-Options: DENY, e Strict-Transport-Security Cabeçalhos.
  • Solicitar limite do corpo: 1 MiB, incluindo pedidos em fluxo ou em blocos.
  • Os terminais Webhook devem usar o HTTPS.
  • Verifique assinaturas do webhook contra os bytes brutos exatos e compare digestos em tempo constante.
  • Os nomes e descrições dos produtos são analisados contra uma lista de bloqueio de conteúdo proibida.
  • Mais 5 violações de conteúdo em 24 horas suspenderá suas chaves do API.

Limites de chaves de API

  • Max 3 chaves criadas por janela de 10 minutos.
  • Max 10 chaves criadas por janela de 1 hora.
  • Max 25 chaves activas por comerciante.