Este guia leva você de zero a um pagamento de teste funcional. Vai carregar a SDK, renderizar o botão e ligar um backend mínimo que cria a ordem e recebe o webhook.
A usar IA para programar? Salte para Construir com IA e copie um prompt pronto que faz toda esta integração por si.

1. Configure as variáveis de ambiente

CLIENT_SECRET e WEBHOOK_SECRET ficam só no backend. Apenas a PUBLISHABLE_KEY pode ir para o frontend. O applicationUUID usado na API é a PUBLISHABLE_KEY sem o prefixo pk_test_/pk_live_.

2. Carregue a SDK no frontend

<script src="https://cdn.faciconnect.com/sdks/v1/facipay.min.js"></script>
<div id="facipay-button-container"></div>

3. Renderize o botão

O container tem de existir no DOM antes do .render().
const facipay = FaciPay(PUBLISHABLE_KEY);

facipay.generateButton({
  async createOrder() {
    const r = await fetch('/api/facipay/create-order', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ items: cart }),
    });
    if (!r.ok) throw new Error('Falha ao criar ordem');
    const { referenceNumber } = await r.json();
    return referenceNumber; // string não-vazia, obrigatório
  },

  async onApprove(data, actions) {
    actions.onPopupWindowClosed(() => {
      window.location.href = `/sucesso?orderId=${data.payment.orderId}`;
    });
  },

  async onPending(data, actions) {
    actions.onPopupWindowClosed(() => {
      const ref = data.payment?.data?.paymentReference;
      const entity = data.payment?.data?.entity?.number;
      window.location.href = `/pendente?ref=${ref}&entity=${entity}`;
    });
  },

  async onCancel() { window.location.href = '/cancelado'; },
  async onError(e) { console.error('FaciPay error:', e); },

  options: {
    style: { width: '100%', shape: 'pill' },
    config: { lang: 'pt', showAmount: true },
    paymentConfig: {
      theme: 'light',
      allowedPaymentMethods: ['FPMCXEXPRSS', 'FPSOLPGEXT', 'FPSOLPG'],
      showUIOfProcessingInfo: true,
      referencePaymentLifeSpan: 1440,
    },
  },
}).render('#facipay-button-container');

4. Crie a ordem no backend

O frontend chama este endpoint dentro do createOrder(). Recalcule o total no servidor.
Node.js (Express)
import express from 'express';

let cachedToken = null; // cache em memória até ~60s antes de expirar

async function getAccessToken() {
  if (cachedToken && cachedToken.exp > Date.now()) return cachedToken.value;
  const basic = Buffer.from(
    `${process.env.FACIPAY_CLIENT_ID}:${process.env.FACIPAY_CLIENT_SECRET}`
  ).toString('base64');

  const res = await fetch(`${process.env.FACIPAY_API_URL}/token`, {
    method: 'POST',
    headers: {
      Authorization: `Basic ${basic}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: 'grant_type=client_credentials&validity_period=3600',
  });
  const json = await res.json();
  cachedToken = { value: json.access_token, exp: Date.now() + 3540 * 1000 };
  return cachedToken.value;
}

app.post('/api/facipay/create-order', express.json(), async (req, res) => {
  const amount = recalcTotalFromItems(req.body.items); // inteiro AOA, no servidor
  const externalTransactionId = 'order_' + crypto.randomUUID();
  await db.orders.insert({ externalTransactionId, amount, status: 'PEN' });

  const token = await getAccessToken();
  const applicationUUID = process.env.FACIPAY_PUBLISHABLE_KEY.replace(/^pk_(test|live)_/, '');

  const fp = await fetch(`${process.env.FACIPAY_API_URL}/facipaypartner/createPaymentOrder`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Accept-Language': 'pt',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      externalTransactionId,
      clientId: req.user?.id ?? 'guest',
      applicationUUID,
      name: 'Compra',
      amount,
      quantity: 1,
    }),
  });

  const { data } = await fp.json();
  await db.orders.update(externalTransactionId, { referenceNumber: data.referenceNumber });
  res.json({ referenceNumber: data.referenceNumber }); // string não-vazia
});

5. Receba o webhook

O webhook é a fonte da verdade. Valide o HMAC sobre o body cru antes de JSON.parse.
Node.js (Express)
import crypto from 'node:crypto';

// Registe o parser raw SÓ nesta rota
app.post('/api/facipay/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const raw = req.body; // Buffer
    const token = req.headers['x-facipay-content-token'];
    const expected = crypto
      .createHmac('sha256', process.env.FACIPAY_WEBHOOK_SECRET)
      .update(raw)
      .digest('hex');

    const a = Buffer.from(String(token));
    const b = Buffer.from(expected);
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.status(401).end(); // assinatura inválida
    }

    const payload = JSON.parse(raw.toString('utf8'));
    const { externalTransactionId, paymentStatus } = payload.data;

    // Idempotência: se já está em estado final, responde 200 e sai
    // Atualiza a ordem: CON = pago, CAN = cancelado, PEN = pendente
    updateOrder(externalTransactionId, paymentStatus);

    res.status(200).json({ received: true });
  }
);
O webhook é global / ao nível da conta: forneça o seu URL de webhook público à equipa FaciPay para que o registe contra as suas credenciais (no onboarding ou via suporte). Não há campo webhookUrl na ordem — o mesmo endpoint recebe todas as suas notificações. Em desenvolvimento, exponha o seu webhook com um túnel (ngrok, Cloudflare Tunnel) e dê esse URL público à FaciPay para o registar.

6. Teste o fluxo

1

Multicaixa Express

Clique no botão → popup → confirme. Dispara onApprove e o webhook chega com CON.
2

Referência EMIS

Escolha Referência → o popup mostra entidade + referência. Dispara onPending.
3

Cancelar

Feche o popup sem pagar. Dispara onCancel.

Próximo passo: Conceitos essenciais

Chaves, ambientes, ciclo de vida da ordem e idempotência.