Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.stric.io/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks são a forma recomendada de saber quando algo muda na sua conta: Pix recebido (pix.in.*), ciclo da transferência Pix enviada (pix.payout.*), cobranças (pix.charge.*). A Stric envia um HTTP POST para cada URL registrada.

1. Registrar um endpoint

curl -X POST "$STRIC_BASE_URL/v1/pix-accounts/$ACCOUNT_ID/webhooks" \
  -H "Authorization: Bearer $STRIC_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.minhaempresa.com/webhooks/stric",
    "events": [
      "pix.in.received",
      "pix.payout.succeeded",
      "pix.charge.paid",
      "pix.charge.expired"
    ],
    "authToken": "um-segredo-forte-gerado-por-voce"
  }'
O authToken só aparece na resposta de criação. Em listagens subsequentes, ele vem mascarado em authTokenPreview. Salve-o no momento da criação.

2. Validar a assinatura

A Stric envia o token que você registrou no header Authorization da requisição de webhook. Compare com o valor que você guardou:
import express from "express";
import crypto from "node:crypto";

const app = express();

function timingSafeEqualUtf8(a, b) {
  if (typeof a !== "string" || typeof b !== "string" || a.length !== b.length) {
    return false;
  }
  const ab = Buffer.from(a, "utf8");
  const bb = Buffer.from(b, "utf8");
  return ab.length === bb.length && crypto.timingSafeEqual(ab, bb);
}

app.post(
  "/webhooks/stric",
  express.json(),
  (req, res) => {
    const incoming = req.headers.authorization;
    const expected = `Bearer ${process.env.STRIC_WEBHOOK_TOKEN}`;

    if (!timingSafeEqualUtf8(incoming ?? "", expected)) {
      return res.status(401).end();
    }

    // `req.body.event` === header `webhook-event-type`; o payload útil está em `req.body.data`
    saveEvent(req.body);

    // Responda 2xx rápido — processamento real fica em fila
    res.status(200).end();

    enqueue(req.body);
  }
);

Headers enviados em cada entrega

HeaderDescrição
AuthorizationQuando você definiu authToken ao registrar: Bearer <authToken> (valide igual ao exemplo acima).
Content-Typeapplication/json
webhook-event-typeMesmo valor que o campo event no JSON do body (ex.: pix.in.received). Útil pra rotear antes de parsear o corpo.

3. Boas práticas no handler

Endpoints lentos podem ser considerados como falha. Persista o evento e responda imediatamente — processe assincronamente em fila.
A Stric pode reentregar um evento. Deduplique pelo eventId (quando existir no JSON) ou por um par estável dentro de data — ex.: endToEndId, { chargeId, endToEndId }, { payoutId, refundEndToEndId } em refunds com múltiplos parciais.
Use crypto.timingSafeEqual (Node) ou hmac.compare_digest (Python). Nunca ==.
Guarde o JSON completo. Se sua lógica mudar, você pode reprocessar eventos passados.

Tipos de evento (events)

Ao registrar o webhook, cada string deve ser exatamente um dos nomes abaixo.

Pix recebido (cash-in direto na conta)

EventoSignificado
pix.in.receivedPix creditado na conta (sem fluxo de cobrança pré-cadastrado).

Transferência Pix enviada

Os nomes dos eventos permanecem pix.payout.* na API; aqui chamamos o fluxo de transferência Pix (envio para terceiros).
EventoSignificado
pix.payout.succeededLiquidação concluída com sucesso.
pix.payout.failedFalha definitiva (reason com código quando aplicável).
pix.payout.cancelledPagamento cancelado (reason pode ser null).
pix.payout.refundedEstorno relacionado à transferência (parcial ou total). Veja exemplos abaixo.

Cobrança (charge / QR Cobrança)

EventoSignificado
pix.charge.paidCobrança paga.
pix.charge.expiredCobrança expirou sem pagamento.
pix.charge.refundedEstorno relacionado à cobrança (parcial ou total). Veja exemplos abaixo.

Corpo da entrega (JSON)

Cada POST traz um objeto com:
  • event: string igual ao tipo assinado (e ao header webhook-event-type).
  • data: objeto com os campos específicos daquele evento (Pix recebido, transferência, cobrança, etc.).
Os exemplos abaixo mostram o corpo completo enviado pela Stric.
event / webhook-event-type: pix.in.received
{
  "event": "pix.in.received",
  "data": {
    "payerName": "Pedro Ferreira de Souza",
    "endToEndId": "E18236120202605041049s078600ecc6",
    "movementId": "cmor2uwya002inxqkoyow2167",
    "receivedAt": "2026-05-04T10:49:34.558Z",
    "amountCents": "120",
    "payerBranch": "1",
    "paymentDate": "2026-05-04 10:49:33.995783+00:00",
    "payerAccount": "49773517",
    "pixAccountId": "cmokguchz0004duqk7pj3f2e4",
    "coreAccountId": "cmokguchu0003duqkodsv49bx",
    "payerDocument": "17227372361",
    "payerInstitution": "18236120"
  }
}
event / webhook-event-type: pix.payout.succeeded
{
  "event": "pix.payout.succeeded",
  "data": {
    "payoutId": "cmoul96ld003w5vqkgrmqky77",
    "endToEndId": "E373198592026050621476T92Q4O7ZJF",
    "movementId": "cmoul96kx003t5vqkzkaqzskl",
    "occurredAt": "2026-05-06T21:48:08.809Z",
    "amountCents": "1"
  }
}
event / webhook-event-type: pix.payout.failed
{
  "event": "pix.payout.failed",
  "data": {
    "payoutId": "cmor35abc002vnxqkp1q9wzj4",
    "movementId": "cmor35abc0030nxqk5kf2x8d1",
    "endToEndId": "E18236120202605041102k08aabb1234",
    "amountCents": "5000",
    "occurredAt": "2026-05-04T11:02:14.227Z",
    "reason": "recipient_not_found"
  }
}
event / webhook-event-type: pix.payout.cancelled
{
  "event": "pix.payout.cancelled",
  "data": {
    "payoutId": "cmor36def0031nxqkqr2xahg9",
    "movementId": "cmor36def0035nxqk7a8tc4e2",
    "endToEndId": "E18236120202605041105m08c12cd567",
    "amountCents": "2500",
    "occurredAt": "2026-05-04T11:05:42.918Z",
    "reason": null
  }
}
event / webhook-event-type: pix.payout.refundedDentro de data, amountCents é o valor original da transferência e refundAmountCents o valor deste estorno. Para refund parcial, compare refundAmountCents < amountCents. Estornos parciais geram vários webhooks separados — deduplique por par estável (ex.: refundEndToEndId); se existir eventId no envelope, use-o também (referência conceitual: transferencia-refunded-{payoutId}-{refundEndToEndId}).Refund parcial:
{
  "event": "pix.payout.refunded",
  "data": {
    "payoutId": "cmor37ghi0040nxqk83df9k1m",
    "movementId": "cmor37ghi0044nxqkpw5lzny6",
    "endToEndId": "E31841474202605041107T08DEF12345",
    "amountCents": "250",
    "occurredAt": "2026-05-04T11:07:23.488Z",
    "refundEndToEndId": "D00416968202605041108UpxYi1K5gFV",
    "refundAmountCents": "100"
  }
}
Refund total (valor do estorno = valor original):
{
  "event": "pix.payout.refunded",
  "data": {
    "payoutId": "cmor38jkl0050nxqkk3ce8fr1",
    "movementId": "cmor38jkl0054nxqkx2td8j6n",
    "endToEndId": "E18236120202605041109K08FGH67890",
    "amountCents": "5000",
    "occurredAt": "2026-05-04T11:09:05.612Z",
    "refundEndToEndId": "D31841474202605041110ZqRm2L6hHWb",
    "refundAmountCents": "5000"
  }
}
event / webhook-event-type: pix.charge.paid
{
  "event": "pix.charge.paid",
  "data": {
    "tag": null,
    "txid": "a7b2507c757f4881859d6f72efc4309d",
    "chargeId": "cmor30g1s002ungqkovmpy3b7",
    "endToEndId": "E18236120202605041054s07e2c3edef",
    "movementId": "cmor30wh50036nxqkuzw3fqpv",
    "occurredAt": "2026-05-04T10:54:13.879Z",
    "amountCents": "300",
    "refundEndToEndId": null
  }
}
event / webhook-event-type: pix.charge.refundedMesma convenção que em pix.payout.refunded, nos campos dentro de data: amountCents = valor original da cobrança liquidada; refundAmountCents = valor deste refund. Exemplo para R100,00pagos("10000"centavos)comrefunddeR 100,00** pagos (**`"10000"`** centavos) com refund de **R 25,00:
{
  "event": "pix.charge.refunded",
  "data": {
    "tag": "pedido-9821",
    "txid": "8f3b22c1d4e74d6a9e1f55a62efa7b3c",
    "chargeId": "cmor39mno0060nxqkzz1kr7p3",
    "movementId": "cmor39mno0064nxqkqo2lc8h7",
    "endToEndId": "E18236120202605041112s08efgh4321",
    "refundEndToEndId": "D31841474202605041113XyAb3M8jJYc",
    "amountCents": "10000",
    "occurredAt": "2026-05-04T11:13:18.954Z",
    "refundAmountCents": "2500"
  }
}
Refund total:
{
  "event": "pix.charge.refunded",
  "data": {
    "tag": null,
    "txid": "1c4a7e9f2b8d4e3a9c5f1b6d2e7a3c8f",
    "chargeId": "cmor3aqrs0070nxqkb1d2ef34",
    "movementId": "cmor3aqrs0074nxqkv4w2l5m8",
    "endToEndId": "E18236120202605041115z08ijkl9876",
    "refundEndToEndId": "D00416968202605041116KkLm4N9pPMd",
    "amountCents": "10000",
    "occurredAt": "2026-05-04T11:16:42.103Z",
    "refundAmountCents": "10000"
  }
}
Múltiplos refunds parciais disparam um webhook cada; quando o valor acumulado cobre amountCents em data, o status da cobrança evolui para REFUNDED. tag em data repete o tag opcional enviado no POST /v1/charges.
event / webhook-event-type: pix.charge.expired
{
  "event": "pix.charge.expired",
  "data": {
    "tag": null,
    "txid": "7c2bb88ca5b842369562de14254c4f2c",
    "chargeId": "cmor2yuob002qnxqksoxtm6sl",
    "occurredAt": "2026-05-04T11:08:00.669Z",
    "amountCents": "1000"
  }
}

Gerenciar endpoints

# Listar
curl "$STRIC_BASE_URL/v1/pix-accounts/$ACCOUNT_ID/webhooks" \
  -H "Authorization: Bearer $STRIC_API_KEY"

# Consultar um
curl "$STRIC_BASE_URL/v1/pix-accounts/$ACCOUNT_ID/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $STRIC_API_KEY"

# Remover
curl -X DELETE "$STRIC_BASE_URL/v1/pix-accounts/$ACCOUNT_ID/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $STRIC_API_KEY"

Testando localmente

Use ngrok ou Cloudflare Tunnel pra expor seu servidor local pra Stric durante o desenvolvimento:
ngrok http <porta-do-seu-servidor>
# O ngrok imprime uma URL HTTPS pública na saída do terminal — use esse host.
Registre essa URL HTTPS pública nos webhooks apenas para testes; em produção use sempre um endpoint estável sob seu próprio domínio.