Ejemplos en Node.js

Implementación minimalista usando solo fetch (Node ≥ 18) y node:crypto. Sin dependencias externas.

Cliente reutilizable

const BASE_URL = 'https://api.internationalfinance.com.br/v1'
 
async function infiRequest(path, { method = 'GET', body, query } = {}) {
  const url = new URL(BASE_URL + path)
  if (query) for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v)
 
  const headers = { Authorization: `Bearer ${process.env.INFI_API_KEY}` }
  if (body !== undefined) headers['Content-Type'] = 'application/json'
 
  const res  = await fetch(url, {
    method,
    headers,
    body: body !== undefined ? JSON.stringify(body) : undefined,
  })
  const json = await res.json().catch(() => ({}))
 
  return { status: res.status, ok: res.ok, body: json }
}
 
// Uso: crear cobro
const charge = await infiRequest('/pix', {
  method: 'POST',
  body: {
    amountCents: 1000,
    externalRef: 'pedido-123',
    customer: {
      name: 'Maria Silva',
      email: 'maria@ejemplo.com',
      phone: '11999998888',
      document: { number: '12345678901', type: 'cpf' },
    },
  },
})
if (!charge.ok) throw new Error(charge.body.error)
console.log(charge.body.transactionId, charge.body.pixPayload)
 
// Uso: iniciar retiro (cuidado con 202 = failed_unknown).
// Actualmente solo CPF/CNPJ del titular registrado.
const withdraw = await infiRequest('/withdraw', {
  method: 'POST',
  body: {
    amountCents: 5000,
    pixKey: '12345678901',
    pixKeyType: 'cpf',
  },
})
if (withdraw.status === 202) {
  // failed_unknown — NO reenviar
  console.warn('retiro indeterminado', withdraw.body.transactionId)
} else if (!withdraw.ok) {
  throw new Error(withdraw.body.error)
}

Recibir webhook (Express)

import express from 'express'
import crypto from 'node:crypto'
 
const app = express()
app.use('/webhooks/infi', express.raw({ type: 'application/json' }))
 
app.post('/webhooks/infi', (req, res) => {
  const ts        = req.header('X-Infi-Timestamp') || ''
  const sigHeader = req.header('X-Infi-Signature') || ''
  const sigHex    = sigHeader.startsWith('sha256=') ? sigHeader.slice(7) : ''
 
  const tsNum = Number(ts)
  if (!Number.isFinite(tsNum) || Math.abs(Date.now() / 1000 - tsNum) > 300) {
    return res.status(401).end()
  }
 
  const payload  = `${ts}.${req.body.toString('utf8')}`
  const expected = crypto
    .createHmac('sha256', process.env.INFI_WEBHOOK_SECRET)
    .update(payload)
    .digest('hex')
 
  if (
    sigHex.length !== expected.length ||
    !crypto.timingSafeEqual(Buffer.from(sigHex), Buffer.from(expected))
  ) {
    return res.status(401).end()
  }
 
  const event = JSON.parse(req.body.toString('utf8'))
  // Idempotencia: trata (transactionId, status) como la clave
  // Encola el procesamiento y responde en < 8s.
  res.status(200).end()
})
 
app.listen(3000)