docs

Setup

Instale @zhex/node ou monte um helper fetch reutilizável. Variáveis de ambiente, base URL e headers padrão.

Esta página é o boilerplate canônico que você cola no projeto. Depois daqui, todos os exemplos da doc rodam sem mudança.

Dois caminhos paralelos: o SDK oficial (recomendado em Node) e o zhexFetch helper (se você prefere HTTP puro ou está fora de Node).

Variáveis de ambiente

# .env (NUNCA commitar)
ZHEX_SECRET_KEY=zsk_test_abcd1234efgh5678
ZHEX_WEBHOOK_SECRET=whsec_test_xyz

Nunca commite chaves

zsk_* e whsec_* em .env (gitignored). Para CI/produção, use o secret manager do seu cloud (AWS Secrets Manager, GCP Secret Manager, Vercel/Fly env). A Zhex revoga automaticamente chaves detectadas em commits públicos via GitHub Secret Scanning.

Caminho 1 — SDK oficial

npm install @zhex/node
// lib/zhex.ts
import Zhex from '@zhex/node';

if (!process.env.ZHEX_SECRET_KEY) {
  throw new Error('ZHEX_SECRET_KEY ausente');
}

export const zhex = new Zhex(process.env.ZHEX_SECRET_KEY);
// uso
import { zhex } from '@/lib/zhex';

const intent = await zhex.paymentIntents.create({
  amount: 19900,
  currency: 'brl',
  customer: 'cus_…',
});

Opções do construtor

const zhex = new Zhex(process.env.ZHEX_SECRET_KEY!, {
  apiBase: 'http://localhost:3333',  // dev local; default https://prometheus.zhex.io
  zhexVersion: '2026-04-25',          // pin de versão; default = ZHEX_API_VERSION_DATE
  maxRetries: 3,                      // 0 desliga retry; default 3
  timeoutMs: 80_000,                  // default 80s; pense em charges com 3DS
});

Opções por chamada

await zhex.refunds.create(
  { payment_intent: 'pi_…' },
  {
    idempotencyKey: 'refund:order-42',  // SDK gera UUID se você omitir
    timeoutMs: 30_000,
    maxRetries: 1,
  },
);

Caminho 2 — Helper fetch

Quando você não quer dependência ou está fora de Node:

// lib/zhex-fetch.ts
import { randomUUID } from 'node:crypto';

const BASE_URL = process.env.ZHEX_BASE_URL ?? 'https://prometheus.zhex.io';
const API_VERSION = '2026-04-25';
const SECRET = process.env.ZHEX_SECRET_KEY!;

if (!SECRET) throw new Error('ZHEX_SECRET_KEY ausente');

type ZhexInit = RequestInit & {
  idempotencyKey?: string;
  query?: Record<string, string | number | boolean | undefined>;
};

export class ZhexError extends Error {
  constructor(
    public status: number,
    public details: { type: string; code: string; message: string; param?: string },
    public requestId: string | null,
  ) {
    super(`[${details.code}] ${details.message}`);
    this.name = 'ZhexError';
  }
}

export async function zhexFetch<T = unknown>(
  path: string,
  init: ZhexInit = {},
): Promise<T> {
  const { idempotencyKey, query, headers, ...rest } = init;

  const qs = query
    ? '?' +
      Object.entries(query)
        .filter(([, v]) => v !== undefined)
        .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
        .join('&')
    : '';

  const method = (rest.method ?? 'GET').toUpperCase();
  const isMutating = method === 'POST' || method === 'DELETE';

  const res = await fetch(`${BASE_URL}${path}${qs}`, {
    ...rest,
    headers: {
      Authorization: `Bearer ${SECRET}`,
      'Zhex-Version': API_VERSION,
      ...(isMutating && { 'Idempotency-Key': idempotencyKey ?? randomUUID() }),
      ...(rest.body && !(rest.body instanceof FormData) && {
        'Content-Type': 'application/json',
      }),
      ...headers,
    },
  });

  if (!res.ok) {
    const err = await res.json().catch(() => ({ error: { message: res.statusText } }));
    throw new ZhexError(res.status, err.error, res.headers.get('request-id'));
  }

  return res.json() as Promise<T>;
}

Uso

import { zhexFetch } from '@/lib/zhex-fetch';

// Criar PaymentIntent
const intent = await zhexFetch<{ id: string; status: string }>(
  '/v1/payment_intents',
  {
    method: 'POST',
    body: JSON.stringify({
      amount: 19900,
      currency: 'brl',
      customer: 'cus_…',
    }),
  },
);

// Listar com paginação
const list = await zhexFetch<{ data: any[]; has_more: boolean }>(
  '/v1/payment_intents',
  { query: { limit: 100 } },
);

Versão da API

A Zhex versiona em dois eixos:

  • Major na URL (/v1/...) — só muda em quebras radicais. Hoje sempre v1.
  • Minor por header (Zhex-Version: 2026-04-25) — datas. Mudanças não-breaking saem em datas novas, sua chave fica pinada na data que você escolher.

Sempre pin explícito em produção:

// SDK: passe no construtor
new Zhex(SECRET, { zhexVersion: '2026-04-25' });

// fetch: passe no header
headers['Zhex-Version'] = '2026-04-25';

Configurações que você controla

ConfiguraçãoOnde (SDK)Onde (HTTP)Quando ajustar
Zhex-VersionzhexVersion no construtorZhex-Version headerPin explícito em prod, atualiza em janela controlada
TimeouttimeoutMs no construtor ou callAbortSignal.timeout(ms) no fetchReduzir em endpoints user-facing (5s); aumentar em workers de billing (30-80s)
RetrymaxRetries (default 3)Helper próprio (ver Retry)SDK já cobre; em HTTP, sempre passar Idempotency-Key reutilizada
HTTPS proxy / certfetch custom no construtorundici.Agent ou dispatcher no fetchAmbientes corporativos
TelemetriaSentry/Datadog em try/catchOTel auto-instrumenta fetch em Node 20+Sempre

Boas práticas

  • Helper único. Não espalhe fetch (ou new Zhex(...)) pelo código — exporte um singleton e reuse.
  • Erro tipado. Cape code (não só status). card_declined, insufficient_funds, idempotency_key_in_use são tratáveis diferente.
  • request-id em log. Cada response tem Request-Id no header (e error.requestId no SDK). Log junto do seu próprio request id pra suporte.
  • Timeout sempre. SDK tem default 80s; em fetch puro use AbortSignal.timeout.
Esta página foi útil?

Atualizado em

Nesta página