GGRAFFITPAY
GRAFFIT PAY API

Integre pagamentos PIX sem carregar a complexidade financeira.

Seu backend cria a cobrança, mostra o PIX no seu próprio checkout e recebe a confirmação assinada. A integração pública usa uma API única e dois modelos de produto.

O que você precisa

Para produção, sua integração precisa de apenas três credenciais/configurações.

1 · CHAMADAS À APIAPI Key LIVE · gpk_live_...
2 · RECEBER EVENTOSUma URL HTTPS de webhook
3 · VALIDAR EVENTOSWebhook Secret HMAC
Segurança: API Key e Webhook Secret pertencem ao backend. Nunca coloque essas credenciais em JavaScript do navegador, HTML público ou aplicativo distribuído.

Dois modelos oficiais de integração

MODELO 01 · DIRECT_SALES

Vendas / checkout

Loja, produto, serviço ou cobrança. O comprador paga; sua aplicação não mantém um saldo Graffit individual para ele.

Abrir guia de vendas
MODELO 02 · CUSTOMER_BALANCES

Clientes + saldo separado

Plataformas em que cada usuário precisa ter um customer_id persistente e uma visão de lançamentos/saldo separada.

Abrir guia de plataforma

Credenciais e base da API

Base URL: https://graffitpay.com/v1
Authorization: Bearer gpk_live_...
Content-Type: application/json
Idempotency-Key: pedido-1001

Para consultar o contrato dinâmico liberado para sua própria conta, use GET /v1/documentation.

A API Key identifica sua conta automaticamente. Não envie merchant_id. Use gpk_test_... apenas no ambiente TEST e gpk_live_... apenas em produção.

Dados obrigatórios em pagamentos: payer.name + payer.cpf. Opcionais: payer.email + payer.phone.

01 · DIRECT_SALES — loja, produto e serviço

No DIRECT_SALES, o comprador não precisa passar KYC por padrão para uma compra PIX comum.

Use este modelo quando sua aplicação só precisa cobrar e saber quando o pagamento terminou. Para uma compra PIX comum, o comprador não cria Customer e não passa pelo fluxo de KYC da Graffit. Em DIRECT_SALES, customer_id é opcional.

Seu pedido→ Payment Intent→ QR / PIX copia-e-cola→ PROCESSING→ SUCCEEDED→ liberar pedido
1 · Crie o Payment Intent no seu backend
POST https://graffitpay.com/v1/payment_intents
Authorization: Bearer gpk_live_...
Idempotency-Key: pedido-1001
Content-Type: application/json

{
  "payment_method": "PIX",
  "amount_brl": 10.00,
  "reference": "pedido-1001",
  "payer": {
    "name": "Maria da Silva",
    "cpf": "52998224725",
    "email": "maria@exemplo.com",
    "phone": "+5567999999999"
  },
  "metadata": {
    "order_id": "1001"
  }
}

email, phone e metadata são opcionais. O metadata deve ser um objeto JSON e tem limite de 8 KB.

2 · Mostre o PIX no checkout da sua própria aplicação

Use payment.pix.br_code para o copia-e-cola e payment.pix.qr_url para a imagem do QR. Você também pode gerar seu próprio QR a partir de br_code.

payment.checkout_url é opcional. É apenas um checkout hospedado/fallback. Uma loja com checkout próprio não precisa mostrar botão “Abrir checkout Graffit”.
3 · Acompanhe o Intent
GET https://graffitpay.com/v1/payment_intents/pi_...

Polling de 3–5 segundos pode ser usado como apoio. O webhook é recomendado para atualização instantânea. Pare o polling em SUCCEEDED, FAILED ou EXPIRED.

4 · Libere o produto somente no final

PROCESSING/pix.paid significa que o PIX foi identificado, mas a operação ainda pode estar finalizando. Libere o produto/serviço somente quando o Intent estiver SUCCEEDED ou quando receber payment.completed com intent_status: "SUCCEEDED".

02 · CUSTOMER_BALANCES — cliente persistente + saldo separado

CUSTOMER_BALANCES usa KYC individual aplicável quando o fluxo daquele Customer exigir verificação.

Use quando cada usuário da sua plataforma precisa ficar vinculado a um Customer Graffit fixo. Crie o Customer uma vez e reutilize sempre o mesmo ID.

1 · Crie o Customer
POST https://graffitpay.com/v1/customers
Authorization: Bearer gpk_live_...
Content-Type: application/json

{
  "display_name": "Maria da Silva",
  "email": "maria@exemplo.com",
  "account_type": "INDIVIDUAL",
  "customer_code": "usuario-123"
}

Guarde o id retornado (cus_...) no cadastro do seu usuário. customer_code é opcional.

2 · Quando o seu fluxo exigir KYC individual
POST /v1/customers/:id/live/onboarding
GET  /v1/customers/:id/live/status?sync=true

O primeiro endpoint retorna o estado do onboarding e, quando necessário, um portal seguro para o usuário concluir a verificação. Sua aplicação não recebe documento ou biometria.

3 · Crie o pagamento com o customer_id
POST /v1/payment_intents
Idempotency-Key: recarga-9001

{
  "customer_id": "cus_...",
  "payment_method": "PIX",
  "amount_brl": 100.00,
  "reference": "recarga-9001",
  "payer": {
    "name": "Maria da Silva",
    "cpf": "52998224725"
  }
}
4 · Consulte os lançamentos/saldo Graffit daquele Customer
GET /v1/customers/:id/balances

Esse endpoint representa os lançamentos processados pela Graffit para aquele Customer. Créditos, bônus, pontos ou débitos internos que existam apenas no seu produto continuam sendo responsabilidade do seu sistema.

Resposta do Payment Intent e PIX

Ao criar ou consultar um Intent, use o status do próprio Payment Intent como estado principal da sua aplicação.

{
  "id": "pi_...",
  "object": "payment_intent",
  "mode": "live",
  "payment_method": "PIX",
  "amount_brl": 10,
  "status": "REQUIRES_PAYMENT",
  "reference": "pedido-1001",
  "payment": {
    "id": "pay_...",
    "status": "PIX_PENDING",
    "checkout_url": "https://graffitpay.com/checkout.html?t=...",
    "pix": {
      "mode": "live",
      "txid": "...",
      "br_code": "000201...",
      "status": "pending",
      "qr_url": "https://graffitpay.com/api/public/checkout/.../qr.png",
      "expires_at": "..."
    }
  }
}
CampoUse para
idSalvar o Payment Intent no seu pedido
statusEstado canônico: REQUIRES_PAYMENT, PROCESSING, SUCCEEDED...
payment.pix.br_codePIX copia-e-cola
payment.pix.qr_urlImagem do QR Code
payment.checkout_urlFallback hospedado opcional
fees.graffit_brlTaxa Graffit congelada na cobrança
fees.net_brlLíquido contratual da sua conta

Status que sua aplicação deve tratar

Payment IntentSignificadoAção
CREATINGA cobrança está sendo criada.Aguarde ou repita com a mesma Idempotency-Key.
REQUIRES_PAYMENTPIX disponível e aguardando pagamento.Mostre QR/copia-e-cola.
PROCESSINGPIX identificado e/ou finalização financeira em andamento.Mostre “pagamento identificado/processando”; não libere como final.
SUCCEEDEDOperação concluída pela Graffit.Finalize o pedido/entrega.
FAILEDFalha final.Não entregue; exiba erro e crie nova tentativa quando apropriado.
EXPIREDA cobrança expirou.Gere um novo Payment Intent com nova Idempotency-Key.
Não use payment.status como substituto do Intent. O pagamento interno pode estar PIX_PAID enquanto o Intent corretamente permanece PROCESSING. Para sua lógica de pedido, priorize o status do Payment Intent.

Webhook + HMAC

Configure a URL HTTPS e crie o Webhook Secret HMAC na aba API do Portal Parceiro. A Graffit envia os eventos automaticamente.

EventoUso
payment.createdCobrança criada.
pix.paidPIX identificado. Trate como processamento, não como conclusão final.
payment.completedFinalização concluída. Confirme intent_status === "SUCCEEDED" e finalize seu pedido.

Headers enviados

X-Graffit-Event: payment.completed
X-Graffit-Event-Id: evt_...
X-Graffit-Timestamp: 178...
X-Graffit-Signature: <hex HMAC SHA-256>
X-Graffit-Webhook-Version: 1

Como validar a assinatura

signed_payload = timestamp + "." + RAW_REQUEST_BODY
expected = HMAC_SHA256(WEBHOOK_SECRET, signed_payload)

compare expected com X-Graffit-Signature usando comparação timing-safe
Use o RAW body exato. Valide a assinatura antes de modificar/reformatar o JSON. Rejeite timestamps com mais de 300 segundos e grave X-Graffit-Event-Id para não processar o mesmo evento duas vezes.
Node.js — exemplo de validação
const crypto = require('crypto');

function validGraffitWebhook({ rawBody, timestamp, signature, secret }) {
  const age = Math.abs(Math.floor(Date.now()/1000) - Number(timestamp));
  if (!Number.isFinite(age) || age > 300) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const a = Buffer.from(String(signature || ''), 'hex');
  const b = Buffer.from(expected, 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Payload final real

{
  "id": "evt_...",
  "type": "payment.completed",
  "created_at": "...",
  "data": {
    "payment": {
      "id": "pay_...",
      "payment_intent_id": "pi_...",
      "reference": "pedido-1001",
      "amount_brl": 10,
      "status": "COMPLETED",
      "intent_status": "SUCCEEDED",
      "payment_method": "PIX",
      "payer": {
        "name": "Maria da Silva",
        "cpf": "52998224725",
        "email": "maria@exemplo.com",
        "phone": "+5567999999999"
      }
    }
  }
}

Responda HTTP 2xx rapidamente depois de validar/persistir o evento. Faça tarefas demoradas em fila/background. Se seu endpoint falhar, a Graffit tenta entregar novamente.

Idempotência — evite cobrança duplicada

Use uma chave única e estável para cada tentativa lógica de pagamento. Se sua aplicação sofrer timeout ou perder a resposta do POST, repita o mesmo request com a mesma chave.

Idempotency-Key: pedido-1001

Não gere uma nova chave só porque houve timeout. A mesma chave com payload diferente retorna conflito.

Erros que sua integração deve tratar

HTTP / códigoO que fazer
400 PAYER_NAME_REQUIREDEnvie payer.name.
400 PAYER_CPF_REQUIREDEnvie payer.cpf.
400 PAYER_CPF_INVALIDCorrija o CPF.
400 PAYER_EMAIL_INVALIDCorrija/remova o e-mail opcional.
400 PAYER_PHONE_INVALIDCorrija/remova o celular opcional.
400 INVALID_AMOUNTEnvie valor BRL maior que zero e dentro dos limites da conta.
400 IDEMPOTENCY_KEY_REQUIREDEnvie Idempotency-Key.
409 IDEMPOTENCY_CONFLICTA chave já foi usada com outro payload. Não reutilize para outra compra.
404 PAYMENT_INTENT_NOT_FOUNDConfira o ID e se ele pertence à mesma API Key.
404 CUSTOMER_NOT_FOUNDConfira o customer_id no modelo CUSTOMER_BALANCES.
409 CUSTOMER_LIVE_KYC_REQUIREDConclua o KYC do Customer antes da operação que o exigir.
403 MERCHANT_LIVE_DISABLEDSua conta ainda não está liberada para operar LIVE.
429 CORE_LIVE_RATE_LIMITEDRespeite retry_after_seconds e não replique a cobrança.

Erros usam JSON com error e code. Em erros transitórios, consulte o Payment Intent antes de criar uma nova cobrança.

TEST — valide sem dinheiro real

Na aba API do Portal Parceiro você pode criar uma TEST API Key, simular pagamento e enviar eventos de webhook assinados. Use TEST para validar sua integração antes de trocar para a chave LIVE.

TEST não é dinheiro real. Não use dados ou status de laboratório como comprovante financeiro.