Reconcile API

The ledger, as an API.

Predictable REST resources, idempotent writes, signed webhooks with retries, and daily statements per virtual account. Build against the sandbox before you touch live money.

Available to verified businesses only.

Webhook inspector

Sample data
    Payload
    The problem

    Bank APIs were not designed for platforms.

    • Bank statements arrive as files, not events.
    • Retries without idempotency create duplicate accounts and duplicate payouts.
    • Unsigned callbacks cannot be trusted with money.
    How it works

    Four steps, no manual matching.

    Authenticate

    Use a scoped API key or OAuth 2.0 client credentials.

    Create resources

    Issue accounts, sessions, wallets and payouts with idempotent POSTs.

    Listen

    Receive signed webhooks for every state change, with retries for 3 days.

    Reconcile

    Pull daily CAMT.053, CSV or JSON statements per account.

    Features

    Everything you need, nothing you have to build.

    REST + JSON

    Resource-oriented URLs, standard HTTP verbs, cursor pagination.

    Idempotency

    Idempotency-Key header on every POST; safe retries for 24 h.

    Signed webhooks

    HMAC-SHA256 signatures with timestamp; exponential retry for 3 days.

    Sandbox

    Simulate credits, returns, recalls and name mismatches with test endpoints.

    Statements

    CAMT.053, CSV and JSON per account, daily.

    OAuth 2.0 & API keys

    Client-credentials for server-to-server; scoped, rotatable keys.

    Versioning

    Date-based API versions pinned per key; breaking changes never silent.

    SDKs

    Official Node and Python SDKs available; PHP SDK in beta.

    Developers

    Create a collection account

    Idempotent POST, synchronous response, signed webhook. Full details in the API reference.

    Compliance note

    API keys are scoped per product and per environment. Every request is logged with its request ID and retained for audit.

    Request
    curl https://api.neofin.tech/v1/collection-accounts \
      -H "Authorization: Bearer $NEO_API_KEY" \
      -H "Idempotency-Key: 7f1c2e4a-9b0d-4c55-a1e3-2d6f0b9c8e11" \
      -H "Content-Type: application/json" \
      -d '{
        "currency": "EUR",
        "holder": {
          "type": "business",
          "legal_name": "Helios Merchants B.V.",
          "kyb_id": "kyb_31QZ"
        },
        "external_ref": "mch_8F2K91",
        "rules": {
          "first_party_only": true,
          "max_credit_amount": 5000000
        },
        "labels": { "region": "NL" }
      }'
    Response
    {
      "id": "ca_9Hc2LmQ",
      "object": "collection_account",
      "status": "active",
      "currency": "EUR",
      "iban": "FR7630004012773091526107",
      "bic": "NEOFFRP1",
      "holder": {
        "type": "business",
        "legal_name": "Helios Merchants B.V.",
        "kyb_id": "kyb_31QZ"
      },
      "external_ref": "mch_8F2K91",
      "rules": { "first_party_only": true, "max_credit_amount": 5000000 },
      "labels": { "region": "NL" },
      "created_at": "2026-11-05T14:02:11Z"
    }
    Verify a webhook signature
    import crypto from "node:crypto";
    
    export function verifyNeoSignature(body, header, secret) {
      const parts = Object.fromEntries(
        header.split(",").map((p) => p.split("="))
      );
      const signed = `${parts.t}.${body}`;
      const expected = crypto
        .createHmac("sha256", secret)
        .update(signed)
        .digest("hex");
      const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
      return (
        fresh &&
        crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
      );
    }
    FAQ

    Questions we get about Reconcile API.

    Is there a sandbox?

    Yes. Sandbox access is immediate on request and mirrors the live API, with test endpoints to simulate credits, returns and recalls.

    How do I verify webhooks?

    Each delivery carries a NEO-Signature header with a timestamp and an HMAC-SHA256 of the timestamp and body using your endpoint secret. Reject anything older than five minutes.

    Are webhooks ordered?

    Events may arrive out of order after retries. Use created_at and the resource's current state rather than event order.

    What are the rate limits?

    Default limits are 600 requests per minute per key. A Retry-After header is returned on 429 responses. Higher limits are available on Institutional plans.

    How are breaking changes handled?

    Versions are date-based and pinned per key. New versions are announced in the changelog at least 90 days before any older version is retired.

    Issue your first virtual IBAN this week.

    Sandbox access is immediate. Live accounts follow KYB review of your business.