Finpass Book a demo

DevelopersIntegration API

Connect your agency’s system to Finpass.

Your ERP or CRM stays the source of truth for the contract; Finpass owns the payment. Two directions, one set of rules:

  • In: create or update contracts and buyers by your own reference, in batches of up to 200. Re-sending never duplicates.
  • Back: signed webhooks for confirmed and partial payments, exceptions, overdue instalments, reminder opt-outs and terminations.
  • Pull: list the payments of any period, with receipt links, for daily reconciliation.

The base URL and your API key come with your agency’s setup — the key is generated by the agency owner in the dashboard, under Integration.

Who owns what. The agency's system is the source of truth for the contract: buyer, property, amounts and due dates. Finpass is the source of truth for the payment: payment codes, bank confirmation, matching, receipts and the evidence trail. Finpass never becomes the agency's management system.

Authentication

Each agency has one API key. The owner generates it in the dashboard under Integration → Generate new key. The key is shown once, and generating a new one switches the old one off.

Authorization: Bearer fp_…
Content-Type: application/json

The sync rules

The same rules apply to the API and to spreadsheet imports.

  1. The key is the contract reference from the agency's system (unique per agency), plus the instalment number (seq) inside the contract. Sending the same contract again never duplicates anything.

  2. Create or update. An unknown reference creates the contract. A known reference updates the buyer, the property label and the instalments that changed.

  3. Omitted instalments are left alone. Nothing is deleted because it was missing from a payload. To cancel an instalment, send it with "status": "cancelled".

  4. Money already in motion is protected. These changes are not applied; they come back as a conflict for a person to resolve in the dashboard:

    • a new amount on an instalment that is paid, partly paid, or has a payment code issued;
    • cancelling an instalment that has received money;
    • reopening a cancelled instalment.

    A due-date change is applied as long as the amount stays the same.

  5. Termination is not synced. Sending "status": "terminated" returns a conflict. The termination is done in the dashboard, because it calculates the refund.

  6. The contract currency never changes after the contract is created.

  7. Buyer phone changes apply to future reminders. If the new number already belongs to another buyer of the same agency, the old number is kept and a conflict is returned.

Contract payload

{
  "reference": "LOTE-12-QD-7",
  "status": "active",
  "propertyLabel": "Quadra 7, lote 12",
  "currency": "BRL",
  "buyer": {
    "name": "Maria Souza",
    "phone": "64211234567",
    "document": "123.456.789-00",
    "email": "maria@example.com"
  },
  "installments": [
    { "seq": 1, "amount": "2450.00", "dueDate": "2026-11-10" },
    { "seq": 2, "amount": "2450.00", "dueDate": "2026-12-10" },
    { "seq": 3, "amount": "2450.00", "dueDate": "2027-01-10", "status": "cancelled" }
  ]
}
Field Rule
reference Required, up to 120 characters.
currency ISO 4217, for example BRL, PYG, USD, EUR, NZD.
buyer.phone Digits with country code. +, spaces and dashes are ignored.
installments[].amount Decimal string with a dot ("2450.00"). PYG has no decimals ("920000").
installments[].dueDate YYYY-MM-DD.
installments[].status open (default) or cancelled.

A contract that fails validation is rejected whole, and nothing is written.

Field names, status values and error codes are the same in every language.

Endpoints

POST /api/sync/contracts: batch

Up to 200 contracts per call. Each contract is handled on its own, and the whole batch is recorded as one sync run, visible in the dashboard under Integration.

{ "contracts": [ { …contract… }, { …contract… } ] }

Response 200:

{
  "runId": "5fbda03b-…",
  "total": 3, "created": 1, "updated": 0, "unchanged": 1, "rejected": 1, "conflicts": 0,
  "results": [
    {
      "reference": "LOTE-12-QD-7",
      "status": "created",
      "contractId": "…",
      "changes": { "buyer": true, "property": true,
                   "installments": { "created": 2, "updated": 0, "cancelled": 0, "unchanged": 0 } },
      "conflicts": []
    },
    { "reference": "X-9", "status": "rejected", "error": "telefone_invalido", "changes": { … }, "conflicts": [] }
  ]
}

PUT /api/contracts/{reference}: one contract

The body is the contract payload, and reference may be omitted. The response is a single result:

  • 201 when created;
  • 200 when updated or unchanged;
  • 422 when rejected.

A rejected contract, or one with conflicts, also appears in the dashboard history.

GET /api/contracts/{reference}: current state

Returns the buyer, every instalment with its status (open, settled or cancelled), paidMinor, whether a payment code is issued (chargeIssued), and the contract balance. Use it to check what Finpass holds before or after a sync.

GET /api/sync/runs/{runId}: a past sync run

Returns the run totals and the result of each contract in it.

Error and conflict codes

Code Meaning
referencia_obrigatoria The contract has no reference.
moeda_nao_suportada Currency not supported.
telefone_invalido Buyer phone missing or not a phone number.
email_invalido Buyer email is malformed.
parcelas_obrigatorias The contract has no instalments.
parcelas_demais More than 600 instalments.
numero_de_parcela_invalido seq must be a positive integer.
parcela_repetida:N seq N appears twice.
vencimento_invalido:N Bad due date on instalment N.
valor_invalido:N Bad amount on instalment N.
contrato_nao_esta_ativo The contract was terminated in Finpass.
moeda_do_contrato_nao_muda The currency differs from the existing contract.
contrato_distratado_nao_e_importado A new contract arrived already terminated.
distrato_deve_ser_feito_no_painel (conflict) Terminate it in the dashboard.
parcela_paga_nao_muda (conflict) The instalment is paid.
parcela_com_pagamento_parcial (conflict) The instalment received a partial payment.
parcela_com_cobranca_em_aberto (conflict) A payment code is issued. Retry once it is paid or expired.
parcela_com_pagamento_nao_pode_ser_cancelada (conflict) The instalment has money in it.
parcela_cancelada_nao_volta (conflict) A cancelled instalment is not reopened.
telefone_ja_pertence_a_outro_comprador (conflict) The new phone belongs to another buyer.

Without a technical integration

The dashboard's Import portfolio screen takes a spreadsheet with one row per contract. It follows the same rules: re-importing updates what changed and never duplicates. Accepted columns:

  • contract: contrato or referencia;
  • buyer: comprador or nome, telefone or whatsapp, documento, cpf or ci, email;
  • property: imovel or lote;
  • money and dates: moeda, valor, parcelas, primeiro_vencimento.

Webhooks: Finpass tells your system

Configure them in the dashboard under Integration → Notices for your system:

  1. Set the HTTPS address.
  2. Generate the signing secret. It is shown once.
  3. Send a test.

Events

Event When
payment.confirmed An instalment is fully paid. The bank confirmed it, or a person matched it in the exceptions queue.
payment.partial Money was credited, but the instalment is still open.
exception.created Money arrived that Finpass would not match on its own: unknown payer or reference, wrong amount, or another currency.
installment.overdue An instalment passed its due date and is still open. Sent once per instalment.
buyer.reminders_opted_out / buyer.reminders_opted_in The buyer replied STOP or REMINDERS.
contract.terminated The contract was terminated in the dashboard, with the paid, retained and refund amounts.
ping The test button.

Envelope

{
  "id": "6f1c…",
  "type": "payment.confirmed",
  "createdAt": "2026-10-10T14:32:05.120Z",
  "data": {
    "contract": { "id": "…", "reference": "LOTE-12-QD-7" },
    "installment": { "seq": 37, "amountMinor": 245000, "currency": "BRL", "dueDate": "2026-10-10",
                     "status": "settled", "paidMinor": 245000 },
    "payment": { "amountMinor": 245000, "currency": "BRL", "confirmedBy": "bank",
                 "payerName": "MARIA SOUZA", "bankTransactionId": "E1234…",
                 "receiptUrl": "https://…/v/…" },
    "buyer": { "name": "Maria Souza", "phone": "64211234567", "document": "123.456.789-00" }
  }
}

When the payment was made in another currency, payment also carries "original": { "amountMinor": 70000, "currency": "BRL", "rate": "1314" }. Here R$ 700.00 was paid at a declared rate of 1 BRL = 1,314 PYG, against an instalment in guaraníes.

Amounts are always integers in the currency's smallest unit (amountMinor): 245000 in BRL is R$ 2,450.00; in PYG the smallest unit is the guaraní itself.

Headers

  • Finpass-Event: the event type.
  • Finpass-Delivery: the event id. It is the same on every retry, so use it to ignore duplicates.
  • Finpass-Signature: t=<unix seconds>,v1=<hex>.

Verifying the signature

Compute HMAC-SHA256(secret, "<t>." + raw_body) in hex and compare it with v1 using a constant-time comparison. Reject the request if t is more than 5 minutes old.

const [t, v1] = header.split(',').map((p) => p.split('=')[1])
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
  && Math.abs(Date.now() / 1000 - Number(t)) < 300

Delivery

  • Any 2xx response counts as delivered. Answer quickly, and do the work afterwards.
  • Payments confirmed by the bank are sent within seconds. Everything else goes out within 5 minutes.
  • Failures are retried after 2, 4, 8, 16, 32, 64 and 128 minutes. After the 8th attempt the event is marked "gave up".
  • Every event, its status and the last response code are listed in the dashboard. Any event that was not delivered can be resent with one click.
  • Events are recorded only while an address is configured.

Receipts export (pull)

GET /api/payments?from=YYYY-MM-DD&to=YYYY-MM-DD

Lists every payment credited in the period, both dates inclusive. Each item has:

  • the contract reference and the instalment;
  • the amount, and the original currency when it differs;
  • the payer name and the bank transaction id;
  • the receipt link;
  • the buyer.

Use it if you would rather fetch than receive webhooks, or to reconcile once a day.

The dashboard has the same data as a spreadsheet: Integration → Export receipts. The file uses ; as separator, a decimal comma for Portuguese and Spanish agencies, and a BOM so Excel reads accents correctly.

FinpassGet started

Bring your buyers abroad into one chat.

Finpass is in production. Tell us about your contracts and we’ll show you how it runs with your bank.

  • With your own contracts. The buyer’s chat, the dashboard and how your bank connects.
  • In your language. English, Portuguese or Spanish.
  • Buyers never pay. Agencies subscribe by active contracts.