SDK Node.js / TypeScript

@pitra/sdk é o cliente oficial da API Pitra para Node.js e TypeScript. Sem dependências de runtime, com tipagem completa, erros tipados, timeout, retry e idempotência automática.

Instalação

Requer Node.js 18 ou superior.

npm install @pitra/sdk

Configuração

TypeScript
import { PitraClient } from "@pitra/sdk";

const pitra = new PitraClient({
  apiKey: process.env.PITRA_API_KEY!, // sk_live_* ou sk_test_*
  // baseURL: "https://pitra.com.br/api/public/v1", // opcional
  // timeout: 15000,                                 // opcional (ms)
});
A chave secreta nunca deve ir para o frontend. Use sk_test_* em desenvolvimento: as cobranças são simuladas e não movimentam dinheiro.

Primeira cobrança Pix

TypeScript
const cobranca = await pitra.pix.create({
  amount: 19.9,
  description: "Plano Pro",
  external_id: "pedido-1042",
  expires_in: 3600,
  customer: { email: "cliente@exemplo.com" },
  metadata: { origem: "checkout-proprio" },
});

console.log(cobranca.id);              // pix_...
console.log(cobranca.status);          // pending
console.log(cobranca.pix.copy_paste);  // Pix Copia e Cola
console.log(cobranca.pix.qr_code_base64);

Consultar cobrança

TypeScript
// Pelo id da Pitra
const cobranca = await pitra.pix.get("pix_123");

// Pelo identificador do seu sistema
const mesma = await pitra.pix.getByExternalId("pedido-1042");

if (cobranca.status === "paid") {
  console.log("Pago em", cobranca.paid_at);
}

Listar cobranças

TypeScript
const pagina = await pitra.pix.list({ limit: 20, status: "paid" });

for (const cobranca of pagina.data) {
  console.log(cobranca.id, cobranca.amount);
}

if (pagina.has_more) {
  const proxima = await pitra.pix.list({ limit: 20, starting_after: pagina.next_cursor! });
  console.log(proxima.data.length);
}

Métodos disponíveis

MétodoEndpointDescrição
pitra.pix.create(data, options?)POST /payments/pixCria uma cobrança Pix com QR Code e Copia e Cola.
pitra.pix.get(id, options?)GET /payments/pix/:idConsulta uma cobrança pelo id da Pitra.
pitra.pix.getByExternalId(externalId, options?)GET /payments/pix/external/:externalIdConsulta pelo identificador do seu sistema.
pitra.pix.list(params?, options?)GET /payments/pixLista cobranças com paginação por cursor.
verifyWebhook(input)Verifica a assinatura Pitra-Signature de um webhook.
A API pública ainda não expõe cancelamento de cobrança Pix, portanto o SDK não implementa cancel(). O método será publicado junto com o endpoint correspondente, em uma nova versão do SDK.

Receber webhook

Node.js + Express
import express from "express";
import { verifyWebhook } from "@pitra/sdk";

const app = express();

// O corpo bruto é obrigatório: verifique a assinatura antes de qualquer parser JSON.
app.post("/webhooks/pitra", express.raw({ type: "*/*" }), async (req, res) => {
  try {
    const evento = await verifyWebhook({
      headers: req.headers,
      rawBody: req.body.toString("utf8"),
      secret: process.env.PITRA_WEBHOOK_SECRET!,
    });

    if (evento.type === "pix.paid") {
      // libere o pedido aqui
    }
    res.sendStatus(200);
  } catch {
    res.sendStatus(400);
  }
});

A assinatura usa o header Pitra-Signature: t=<unix>,v1=<hmac_sha256>, com comparação em tempo constante e tolerância padrão de 300 segundos contra replay.

Tratamento de erros

TypeScript
import {
  AuthenticationError,
  ValidationError,
  RateLimitError,
  NotFoundError,
  ServerError,
  NetworkError,
  TimeoutError,
  isApiError,
  isTimeout,
} from "@pitra/sdk";

try {
  await pitra.pix.create({ amount: 19.9 });
} catch (err) {
  if (err instanceof ValidationError) {
    console.error("Payload inválido:", err.code, err.message);
  } else if (err instanceof AuthenticationError) {
    console.error("Chave inválida ou revogada");
  } else if (err instanceof RateLimitError) {
    console.error("Aguarde", err.retryAfter, "segundos");
  } else if (isTimeout(err)) {
    console.error("Timeout — consulte pelo external_id antes de recriar");
  } else if (isApiError(err)) {
    console.error(err.status, err.code, err.requestId);
  } else {
    throw err;
  }
}

Todo erro traz status, code, message e requestId quando a API devolver. Informe o requestId ao suporte.

Timeout

TypeScript
// Timeout padrão do cliente
const pitra = new PitraClient({ apiKey, timeout: 15000 });

// Timeout de uma chamada específica
await pitra.pix.get("pix_123", { timeout: 5000 });

Implementado com AbortController. Ao estourar, o SDK lança TimeoutError.

Retry

No máximo uma tentativa adicional, apenas em timeout, erro de rede, 429, 500, 502, 503 e 504. Nenhum outro erro 4xx é repetido automaticamente. A retentativa reenvia a mesma chave de idempotência, então não há risco de cobrança duplicada.

Idempotência

TypeScript
// Chave definida por você
await pitra.pix.create({ amount: 19.9 }, { idempotencyKey: "pedido-1042" });

// Sem informar nada, o SDK gera uma UUID v4 e envia em Idempotency-Key
await pitra.pix.create({ amount: 19.9, external_id: "pedido-1042" });

Além da chave, reutilizar o mesmo external_id devolve a cobrança existente em vez de criar outra.

Helpers

HelperO que faz
formatMoney(19.9)Formata o valor no padrão brasileiro: R$ 19,90.
generateIdempotencyKey()Gera uma UUID v4 para usar como Idempotency-Key.
isApiError(err)Indica se o erro veio do SDK ou da API Pitra.
isTimeout(err)Indica se o erro foi causado por timeout.

Exemplos por framework

Cada exemplo tem link direto — por exemplo /docs/sdk#express ou /docs/sdk#cloudflare-workers.

Express#

TypeScript
import express from "express";
import { PitraClient, verifyWebhook } from "@pitra/sdk";

const app = express();
const pitra = new PitraClient({ apiKey: process.env.PITRA_API_KEY! });

app.post("/pedidos", express.json(), async (req, res) => {
  const cobranca = await pitra.pix.create({
    amount: 49.9,
    description: "Pedido 1042",
    external_id: "pedido-1042",
    expires_in: 1800,
    customer: { email: "cliente@exemplo.com", whatsapp: "11999998888" },
  });
  res.json({ id: cobranca.id, copy_paste: cobranca.pix.copy_paste });
});

// A verificação da assinatura exige o corpo bruto.
app.post("/webhooks/pitra", express.raw({ type: "*/*" }), async (req, res) => {
  try {
    const evento = await verifyWebhook({
      headers: req.headers,
      rawBody: req.body.toString("utf8"),
      secret: process.env.PITRA_WEBHOOK_SECRET!,
    });
    if (evento.type === "pix.paid") { /* libere o pedido */ }
    res.sendStatus(200);
  } catch {
    res.sendStatus(400);
  }
});

Fastify#

TypeScript
import Fastify from "fastify";
import { PitraClient, verifyWebhook } from "@pitra/sdk";

const app = Fastify();
const pitra = new PitraClient({ apiKey: process.env.PITRA_API_KEY! });

app.post("/pedidos", async () => {
  const cobranca = await pitra.pix.create({
    amount: 49.9,
    description: "Pedido 1042",
    external_id: "pedido-1042",
    expires_in: 1800,
    customer: { email: "cliente@exemplo.com", whatsapp: "11999998888" },
  });
  return { id: cobranca.id, copy_paste: cobranca.pix.copy_paste };
});

app.addContentTypeParser("*", { parseAs: "buffer" }, (_req, body, done) => done(null, body));

app.post("/webhooks/pitra", async (req, reply) => {
  const evento = await verifyWebhook({
    headers: req.headers as Record<string, string>,
    rawBody: (req.body as Buffer).toString("utf8"),
    secret: process.env.PITRA_WEBHOOK_SECRET!,
  });
  if (evento.type === "pix.paid") { /* libere o pedido */ }
  return reply.code(200).send();
});

NestJS#

TypeScript
import { Controller, Post, Body, Headers, Req, HttpCode } from "@nestjs/common";
import { PitraClient, verifyWebhook } from "@pitra/sdk";
import type { Request } from "express";

const pitra = new PitraClient({ apiKey: process.env.PITRA_API_KEY! });

@Controller()
export class PagamentosController {
  @Post("pedidos")
  async criar(@Body() _body: unknown) {
    const cobranca = await pitra.pix.create({
      amount: 49.9,
      description: "Pedido 1042",
      external_id: "pedido-1042",
      expires_in: 1800,
      customer: { email: "cliente@exemplo.com", whatsapp: "11999998888" },
    });
    return { id: cobranca.id, copy_paste: cobranca.pix.copy_paste };
  }

  // Registre o rawBody no main.ts: NestFactory.create(AppModule, { rawBody: true })
  @Post("webhooks/pitra")
  @HttpCode(200)
  async webhook(@Req() req: Request & { rawBody: Buffer }, @Headers() headers: Record<string, string>) {
    const evento = await verifyWebhook({
      headers,
      rawBody: req.rawBody.toString("utf8"),
      secret: process.env.PITRA_WEBHOOK_SECRET!,
    });
    return { received: evento.id };
  }
}

Next.js API Route#

TypeScript
// app/api/pedidos/route.ts
import { PitraClient } from "@pitra/sdk";

const pitra = new PitraClient({ apiKey: process.env.PITRA_API_KEY! });

export async function POST() {
  const cobranca = await pitra.pix.create({
    amount: 49.9,
    description: "Pedido 1042",
    external_id: "pedido-1042",
    expires_in: 1800,
    customer: { email: "cliente@exemplo.com", whatsapp: "11999998888" },
  });
  return Response.json({ id: cobranca.id, copy_paste: cobranca.pix.copy_paste });
}

// app/api/webhooks/pitra/route.ts
import { verifyWebhook } from "@pitra/sdk";

export async function POST(request: Request) {
  const rawBody = await request.text(); // texto bruto, antes de qualquer parse
  try {
    const evento = await verifyWebhook({
      headers: Object.fromEntries(request.headers),
      rawBody,
      secret: process.env.PITRA_WEBHOOK_SECRET!,
    });
    if (evento.type === "pix.paid") { /* libere o pedido */ }
    return new Response("ok");
  } catch {
    return new Response("assinatura inválida", { status: 400 });
  }
}

Next.js Server Action#

TypeScript
"use server";

import { PitraClient } from "@pitra/sdk";

const pitra = new PitraClient({ apiKey: process.env.PITRA_API_KEY! });

export async function criarCobranca(pedidoId: string, valor: number) {
  const cobranca = await pitra.pix.create(
    { amount: valor, external_id: pedidoId, expires_in: 1800, customer: { email: "cliente@exemplo.com", whatsapp: "11999998888" } },
    { idempotencyKey: pedidoId },
  );
  return { id: cobranca.id, copyPaste: cobranca.pix.copy_paste, qr: cobranca.pix.qr_code_base64 };
}

Bun#

TypeScript
import { PitraClient, verifyWebhook } from "@pitra/sdk";

const pitra = new PitraClient({ apiKey: Bun.env.PITRA_API_KEY! });

Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);

    if (url.pathname === "/pedidos" && req.method === "POST") {
      const cobranca = await pitra.pix.create({
        amount: 49.9,
        description: "Pedido 1042",
        external_id: "pedido-1042",
        expires_in: 1800,
        customer: { email: "cliente@exemplo.com", whatsapp: "11999998888" },
      });
      return Response.json({ id: cobranca.id, copy_paste: cobranca.pix.copy_paste });
    }

    if (url.pathname === "/webhooks/pitra" && req.method === "POST") {
      const evento = await verifyWebhook({
        headers: Object.fromEntries(req.headers),
        rawBody: await req.text(),
        secret: Bun.env.PITRA_WEBHOOK_SECRET!,
      });
      return Response.json({ received: evento.id });
    }

    return new Response("not found", { status: 404 });
  },
});

Cloudflare Workers#

TypeScript
import { PitraClient, verifyWebhook } from "@pitra/sdk";

type Env = { PITRA_API_KEY: string; PITRA_WEBHOOK_SECRET: string };

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const pitra = new PitraClient({ apiKey: env.PITRA_API_KEY });
    const url = new URL(request.url);

    if (url.pathname === "/pedidos") {
      const cobranca = await pitra.pix.create({
        amount: 49.9,
        description: "Pedido 1042",
        external_id: "pedido-1042",
        expires_in: 1800,
        customer: { email: "cliente@exemplo.com", whatsapp: "11999998888" },
      });
      return Response.json({ id: cobranca.id, copy_paste: cobranca.pix.copy_paste });
    }

    if (url.pathname === "/webhooks/pitra") {
      const evento = await verifyWebhook({
        headers: Object.fromEntries(request.headers),
        rawBody: await request.text(),
        secret: env.PITRA_WEBHOOK_SECRET,
      });
      return Response.json({ received: evento.id });
    }

    return new Response("not found", { status: 404 });
  },
};

Vercel Functions#

TypeScript
// api/pedidos.ts — Vercel Function (runtime Node.js)
import { PitraClient } from "@pitra/sdk";

const pitra = new PitraClient({ apiKey: process.env.PITRA_API_KEY! });

export const config = { runtime: "nodejs" };

export default async function handler(request: Request) {
  if (request.method !== "POST") return new Response("method not allowed", { status: 405 });
  const cobranca = await pitra.pix.create({
    amount: 49.9,
    description: "Pedido 1042",
    external_id: "pedido-1042",
    expires_in: 1800,
    customer: { email: "cliente@exemplo.com", whatsapp: "11999998888" },
  });
  return Response.json({ id: cobranca.id, copy_paste: cobranca.pix.copy_paste });
}

API v1 · OpenAPI 1.0.0 · SDK 0.1.0