# Create Monitoring Schedule Source: https://docs.rightfoot.com/api-reference/monitoring/create-scheduled-balance-request POST /v1/monitoring/scheduled_balance_request Create a recurring scheduled balance request for authorizers # Create Monitoring Schedule Create a recurring scheduled balance request for a set of authorizers. The schedule will automatically run balance checks at the specified interval and send results to your webhook. ## Scheduling Options Use `interval_days` together with `time_of_day` to run balance checks every N days at a specific time. | Field | Description | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `interval_days` | Run every N days (1-30) | | `time_of_day` | Time to run in HH:mm format (24-hour, UTC). Example: `"14:30"` for 2:30 PM UTC. If not provided, defaults to a random time in the 8:00 AM - 11:00 AM UTC window. | ## Key Information
**Schedule Name (Required):** Provide a `name` to identify your schedule. Names must be unique (max 255 characters).
**Start:** The first job runs at the `time_of_day` on the `starts_at` date, after which jobs will run on the provided interval (format: `YYYY-MM-DD`). If none is provided, the first job will run at the next `time_of_day`.
**Expiration:** A final job runs at the scheduled `time_of_day` on the `expires_at` date, after which no more jobs will run (format: `YYYY-MM-DD`, default 30 days from creation, max 365 days)
**Webhook:** Results are delivered to your specified webhook URL after each run
## Threshold Monitoring Set balance thresholds to receive webhook notifications when an account's balance meets or exceeds a specified amount. | Field | Level | Description | | ------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `threshold_amount` | Request | Applies to all authorizers in the batch. When any authorizer's balance meets or exceeds this amount, a webhook notification is sent. | | `threshold_amount` | Authorizer | Set within each authorizer object to define individual thresholds. Overrides the request-level threshold if both are provided. | When any threshold is configured (request-level or authorizer-level), the `webhook` field is required. ## Payment Information Types You must provide either **bank account information** or **debit card information** for each authorizer: ### Bank Account Information * Required: `account_number`, `routing_number`, and either `ssn` or `ein` ### Debit Card Information * Required: `debit_card_number`, and either `ssn` or `ein` * Optional: `cvv`, `expiration_month`, `expiration_year`, `routing_number` (if linked to a bank account) ### Optional Fields * `threshold_amount`: Balance threshold in dollars for this authorizer (see [Threshold Monitoring](#threshold-monitoring)) # Retrieve Authorizer Balance History Source: https://docs.rightfoot.com/api-reference/monitoring/get-authorizer-history GET /v1/monitoring/authorizers/{id}/history Retrieve balance check history for a specific authorizer # Retrieve Authorizer Balance History Retrieve the balance check history for a specific authorizer. Returns a list of recent balance results in reverse chronological order (most recent first). ## Use Cases * Review historical balance data for an authorizer * Debug failed balance checks * Analyze balance trends over time ## Response Fields | Field | Description | | ---------------------- | ---------------------------------------------------------- | | `authorizer_unique_id` | The authorizer identifier | | `total_count` | Total number of history records for this authorizer | | `history` | Array of history entries (see below) | | `next_page_token` | Token for fetching the next page (null if no more results) | | `has_more` | Whether more results are available | Each history entry contains: | Field | Description | | ---------------- | -------------------------------------- | | `run_at` | When the balance check was performed | | `batch_id` | The associated batch request ID | | `balance_amount` | The balance retrieved (null if failed) | | `status` | `success` or `failed` | | `error_message` | Error details if the check failed | ## Pagination Use the `limit` query parameter to control how many results are returned: * Default: 10 entries * Maximum: 100 entries To fetch additional pages, pass the `next_page_token` from a previous response as the `next_page_token` query parameter. Continue fetching until `has_more` is `false` or `next_page_token` is null. # Retrieve Schedule Details Source: https://docs.rightfoot.com/api-reference/monitoring/get-schedule GET /v1/monitoring/schedules/{id} Retrieve details about a scheduled balance request # Retrieve Schedule Details Retrieve details about a scheduled balance request, including when it last ran, when it will run next, and how many authorizers are included in the schedule. ## Use Cases * Monitor the status of your scheduled balance requests * Verify when the next balance check will occur * Check how many authorizers are being tracked ## Response Fields | Field | Description | | ------------------- | ---------------------------------------------------------- | | `schedule_id` | The unique identifier for the schedule | | `name` | The name of the schedule, if one was provided | | `last_run_at` | When the schedule last completed a run (null if never run) | | `next_run_at` | When the next scheduled run will occur | | `total_authorizers` | Number of authorizers in this schedule | # Remove Schedule Authorizers Source: https://docs.rightfoot.com/api-reference/monitoring/remove-schedule-authorizers PATCH /v1/monitoring/schedules/{id}/authorizers Remove authorizers from a scheduled balance request # Remove Schedule Authorizers Remove specific authorizers from an existing scheduled balance request. The schedule will continue running for any remaining authorizers. ## Use Cases * Stop monitoring specific accounts while keeping the schedule active for others * Clean up authorizers that are no longer needed * Reduce the scope of an existing schedule without recreating it ## Response Fields | Field | Description | | ----------------- | ------------------------------------------------------------ | | `removed_count` | Number of authorizers successfully removed from the schedule | | `remaining_count` | Number of authorizers still in the schedule after removal | # Update Schedule Source: https://docs.rightfoot.com/api-reference/monitoring/update-schedule PATCH /v1/monitoring/schedules/{id} Update an existing scheduled balance request # Update Schedule Modify the scheduling parameters of an existing scheduled balance request. You can update the interval, time of day, or expiration date. ## Use Cases * Change how frequently balance checks run (e.g., from weekly to daily) * Adjust the time of day when scheduled checks execute * Extend or shorten the schedule's expiration date ## Request Fields All fields are optional, but at least one must be provided. | Field | Description | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | Update the schedule name. Must be unique. Max 255 characters | | `interval_days` | Update how often the schedule runs (1-30 days) | | `time_of_day` | Update the time to run in HH:mm format (24-hour, UTC). Example: "14:30" for 2:30 PM UTC | | `expires_at` | Update the schedule expiration date. A final job runs at this time, after which no more jobs will run. Must be in the future. ISO 8601 format | ## Response Fields | Field | Description | | ------------- | --------------------------------------------------------------------------------- | | `schedule_id` | The unique identifier for the schedule | | `name` | The name of the schedule, if one was provided | | `message` | Confirmation message indicating the update was successful | | `expires_at` | The updated expiration date for the schedule | | `next_run_at` | The next scheduled run time. May be null if the schedule is disabled or completed | ## Important Notes * The schedule must be in **ACTIVE** status to be updated * At least one update field must be provided in the request * The `expires_at` value must be a future timestamp * If you provide a `name` that already exists for another schedule, a **409 Conflict** error is returned # Add a Processor Key Source: https://docs.rightfoot.com/api-reference/payments/create-payment-connection POST /v1/payment_connections Register a processor API key and choose which borrowers and payment types it takes # Add a Processor Key Rightfoot collects payments through your own merchant accounts at a [supported payment processor](#supported-processors). Register the API key of each account you hold, and tell Rightfoot which borrowers and which payment types it should be used for. Paste the key once. Rightfoot stores it in a vault the moment the request is accepted; from then on every response shows only its last four characters, and the key itself is never returned. If the person holding the keys would rather not call the API, the same flow is in the portal — see [Processor Keys in the Portal](/api-reference/payments/processor-keys-portal). ## Supported processors Rightfoot works with the processor you already hold merchant accounts at. Register keys from any processor in this list; every endpoint on these pages behaves the same regardless of which one issued the key. | Processor | Payment types | Where to get the key | | -------------------------------------- | --------------- | --------------------------------------------------------- | | [Payliance](https://www.payliance.com) | ACH, debit card | An API key for each merchant account, issued by Payliance | Using a processor that is not listed? Ask Rightfoot about adding it. Nothing about how you enroll borrowers changes when a processor is added — only where the keys come from. ## Default keys and tagged keys Which key a payment goes through is decided by two things you set when you add it. Used for every borrower you enroll **without** a `merchant_ref`. Set `is_default: true`. You can hold one default per payment type — ACH, debit card, or a single key that covers both. Used only for borrowers you enroll **with** that tag. Set `merchant_ref` to a short slug such as `acme-nv`, and send the same value as `merchant_ref` when you [enroll](/api-reference/payments/create-payment-enrollments) those borrowers. A key is one or the other, never both — a request with `is_default: true` and a `merchant_ref` is rejected. A key with neither is stored but not used for anything until you [promote it](/api-reference/payments/update-payment-connection#make-a-key-the-default). **Tags are permanent.** Borrowers already enrolled under a tag would silently move to a different merchant account if the tag could change, so `merchant_ref` is fixed once saved. The key behind a tag is not — [replace it](/api-reference/payments/update-payment-connection#replace-a-key) any time and the tag stays. ## One default per payment type `rail_scope` says which payment types a key can take: `ach`, `card` (debit card), or `both`. It is required and has no default, because a key used for a payment type its merchant account was not set up for fails at the processor rather than here. You can hold one default key per `rail_scope`. When a payment type is covered by both a `both` default and a type-specific default, the type-specific one wins — a dedicated ACH key takes the ACH payments, and the `both` key keeps covering debit card. A payment type with no default key skips every borrower enrolled without a tag. [List your keys](/api-reference/payments/list-payment-connections) to see which types are uncovered in `missing_default_rails`. One tag can hold one key per `rail_scope`, so `acme-nv` may have both an ACH key and a debit-card key if that merchant account is split by payment type at the processor. ## When a request is rejected Conflicts are checked before the key is stored, so a rejected request leaves nothing behind. Send it again once corrected. | Status | When | | ------ | --------------------------------------------------------------------------------------------------------------------- | | `400` | A required field is missing, `merchant_ref` is not a valid tag, or `is_default` and `merchant_ref` were sent together | | `409` | A default key for that `rail_scope` already exists, or that tag already has a key for that `rail_scope` | The `error.message` in the response says which — for example `A default key for ACH already exists` or `Tag 'acme-nv' already has an ACH key`. # Enroll and Update Borrowers Source: https://docs.rightfoot.com/api-reference/payments/create-payment-enrollments POST /v1/payment_enrollments Enroll borrowers and update them by re-sending — one route for your whole file # Enroll and Update Borrowers Enroll a batch of borrowers in a single call. Rightfoot stores each borrower, monitors their balance, and acts once there is enough in the account to collect. This is also the update route: send a borrower you have already enrolled and their details and amount are refreshed in place. There is no separate update call. Send up to 1000 borrowers per request. ## Send your whole file, every day This endpoint is an **upsert** — re-sending a borrower is how you update them. Someone already being monitored comes back as `updated`: their details are refreshed and the `collectable_amount` in that request is applied to the monitor they already have. That means you do not need to track what you have already sent, or which borrowers changed. Push your full delinquent file on whatever cadence suits you and let the response tell you what happened to each borrower. To stop monitoring a borrower altogether, use [Remove Borrowers from Monitoring](/api-reference/payments/delete-payment-enrollments). ### What a re-send changes | Sent again | Effect on a monitored borrower | | -------------------------------- | ----------------------------------------------------------------------------------------------- | | `collectable_amount` | Applied to the existing monitor. Rightfoot works toward the new figure from the next payment on | | Identity and bank account fields | Re-stored. The borrower keeps a single record however many times you send them | | `merchant_ref` | Replaced | | `charge_instrument` | Replaced. Omitting it for a borrower you previously sent with a card moves them back to ACH | This is what to use when a borrower pays you directly and the amount you will accept drops: send them again with the lower `collectable_amount`. An update applies to the **next** payment, not to one already in flight. If Rightfoot has a payment out at the bank for this borrower, that payment runs to its original amount, and the new amount is picked up only if it comes back. An unchanged re-send is reported `updated` as well. Rightfoot cannot tell "re-sent unchanged" from "re-sent with edits" — the stored details are encrypted and cannot be read back to compare — so read `updated` as "this borrower is monitored against the amount you just sent", not as "something about them changed". ## Monitoring lapses if you stop sending Monitoring expires 30 days after it starts. Sending a borrower again after their monitoring has expired starts fresh monitoring, using the `collectable_amount` in that request. Re-sending a borrower who is *still* being monitored updates them but does not extend that window — the 30 days run from when monitoring started, not from your last push. Your regular push is therefore what keeps monitoring alive. If a borrower drops out of your file, monitoring for them lapses within 30 days. ## What you send for each borrower The identity and bank account fields are the same ones the balance endpoints accept, so an existing integration can reuse its serialization. | Field | Required | Notes | | ---------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- | | `authorizer_unique_id` | Yes | Your own identifier for this borrower | | `first_name`, `last_name` | Yes | | | `phone_number` | Yes | | | `date_of_birth` | Yes | Object with `day`, `month`, `year` | | `address` | Yes | Only `zip_code` is required within it | | `ssn` or `ein` | Yes | One of the two | | `account_number`, `routing_number` | Yes | See below | | `collectable_amount` | Yes | Cents. The most you will accept today | | `days_past_due` | Yes | As of this request | | `merchant_ref` | No | The tag of the [processor key](/api-reference/payments/create-payment-connection) to settle to. Omit it to use your default key | | `charge_instrument` | No | See [How Rightfoot collects](#how-rightfoot-collects) | **Keep `authorizer_unique_id` stable for the life of the loan.** Monitoring history accumulates against it. An identifier that changes between pushes resets that history, and monitoring quietly degrades rather than failing. ### The bank account is always required `account_number` and `routing_number` are required for every borrower, including those you intend to charge by card. They are what Rightfoot uses to read the balance, and no charge instrument substitutes for them. ### The amount is a ceiling, in cents `collectable_amount` is an integer number of cents — `42500` is \$425.00. Rightfoot treats it as a **ceiling**, and will not attempt more than that in a single payment. ## How Rightfoot collects Omit `charge_instrument` entirely and Rightfoot collects by ACH from the bank account you supplied. That is the common case. To have a card charged instead, supply exactly one of: A token held by your payment processor. Preferred — no sensitive card data leaves your systems. Send `card_last4` alongside it. Raw card details, stored securely by Rightfoot. Use this only when you cannot produce a processor token. `card_last4` is required with `processor_token`. The token itself is opaque to Rightfoot, so the last four digits are what confirm the card belongs to the account being monitored — a card drawing on a different account would make the balance Rightfoot reads meaningless. Each borrower in the same request can use a different option. Only the `charge_instrument` differs — the identity and bank account fields are the same either way. ```json ACH theme={null} { "authorizer_unique_id": "1a2b3c4d", "account_number": "1234567890", "routing_number": "021000021", "collectable_amount": 42500, "days_past_due": 47 } ``` ```json Processor token theme={null} { "authorizer_unique_id": "5e6f7g8h", "account_number": "9876543210", "routing_number": "071000013", "collectable_amount": 18000, "days_past_due": 22, "charge_instrument": { "processor_token": "tok_9f3a21c7", "card_last4": "4412" } } ``` ```json Raw card theme={null} { "authorizer_unique_id": "9i0j1k2l", "account_number": "5544332211", "routing_number": "111000025", "collectable_amount": 7500, "days_past_due": 9, "charge_instrument": { "card": { "debit_card_number": "4111111111111111", "cvv": "123", "expiration_month": 4, "expiration_year": 2030 } } } ``` These snippets show only the fields that differ. Identity fields (`first_name`, `last_name`, `phone_number`, `date_of_birth`, `address`, and `ssn` or `ein`) are required on every borrower — see the full example above. ## Reading the response The response is `207`, and every borrower you sent has an entry in `results`, in the order you sent them. **Check each `outcome` rather than relying on the status code** — one bad record never blocks the rest of the batch. | Outcome | What it means | What to do | | ---------- | --------------------------------------------------------------------------------------- | ------------ | | `enrolled` | Stored, and monitoring started | Nothing | | `updated` | Monitoring was already running; details refreshed and the amount you sent applied to it | Nothing | | `held` | Stored, but not monitored — Rightfoot cannot read this bank | See `reason` | | `failed` | The details in this request were not stored | See `reason` | `already_enrolled` is no longer returned. It stays in the response as a count that is always `0` so that an integration parsing the old shape keeps working — a re-sent borrower is reported `updated` instead. ### Held borrowers A `held` borrower's details are kept. Monitoring simply did not start, because Rightfoot cannot reach their bank. | Reason | Meaning | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `institution_not_supported` | Rightfoot does not currently support this bank. Your data is correct; there is no action to take. Keep sending them — monitoring starts automatically if support is added. | | `unresolvable_routing_number` | The routing number did not match a known bank. Check it. | ### Failed borrowers | Reason | Meaning | | -------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `duplicate_in_request` | This identifier appeared more than once in the request. Every copy is rejected rather than guessing which you meant. | | `tokenization_failed` | The borrower's details were rejected. `message` names the problem — correct it and send again. | | `card_tokenization_failed` | The card details were rejected. See `message`. | | `chunk_failed` | A temporary failure affecting part of the batch. Send these borrowers again. | | `monitor_open_failed` | The borrower was stored but monitoring did not start. Send them again. | `chunk_failed` and `monitor_open_failed` are both safe to retry — re-sending a borrower who did enroll returns `updated` and re-applies the same amount, rather than creating anything twice. # Delete a Processor Key Source: https://docs.rightfoot.com/api-reference/payments/delete-payment-connection DELETE /v1/payment_connections/{id} Revoke a key and remove it from your account # Delete a Processor Key Revoke the stored key and remove it. The response is `204` with no body. An `id` that is not yours, or that no longer exists, returns `404`. ## Deleting a default key Deleting a default key is always allowed. A payment type it covered falls back to a default `both` key if you have one. Only the types no other default covers are affected: until you [add](/api-reference/payments/create-payment-connection) or [promote](/api-reference/payments/update-payment-connection#make-a-key-the-default) another default for them, borrowers enrolled without a `merchant_ref` are skipped on those types. `GET /v1/payment_connections` lists them in `missing_default_rails`. To swap credentials on a key that is in use, [replace the key](/api-reference/payments/update-payment-connection#replace-a-key) instead of deleting and re-adding it. The tag and default status carry over, and there is no moment with no key. Payment results already read through this key are kept. # Remove Borrowers from Monitoring Source: https://docs.rightfoot.com/api-reference/payments/delete-payment-enrollments DELETE /v1/payment_enrollments Stop monitoring a batch of borrowers without deleting anything about them # Remove Borrowers from Monitoring Stop monitoring a batch of borrowers in a single call. Send up to 1000 identifiers per request — the same `authorizer_unique_id` values you [enrolled](/api-reference/payments/create-payment-enrollments) them with. Use it for cured accounts, payoffs, and any borrower who must not be contacted or debited. Removal takes effect immediately: no new payment is initiated for these borrowers after the call returns. Nothing about the borrower is deleted. Their details and payment history are kept, and results already collected remain available. ## Removal is not sticky Sending a removed borrower in a later [enrollment](/api-reference/payments/create-payment-enrollments) push is read as intent to resume, and starts fresh monitoring using the `collectable_amount` in that push. If your enrollment push is a full file of your book, **take removed borrowers out of the file**. A borrower you remove today and re-send tomorrow is monitored again tomorrow — for borrowers removed because they must not be contacted or debited, keeping them out of the push is what keeps them removed. ## Reading the response The response is `200`, and every identifier you sent has an entry in `results`, in the order you sent them. **Check each `outcome` rather than relying on the status code.** | Outcome | What it means | What to do | | --------------- | --------------------------------------------------------- | -------------------------------------------------------- | | `removed` | Live monitoring was stopped by this request | Nothing | | `not_monitored` | Rightfoot knows this borrower but was not monitoring them | Nothing — there was nothing to stop | | `unknown` | Rightfoot has no record of this identifier | **Check the identifier** — this borrower was not removed | `not_monitored` is normal, not an error. Monitoring [expires 30 days after it starts](/api-reference/payments/create-payment-enrollments#monitoring-lapses-if-you-stop-sending), so a borrower who left your enrollment file weeks ago has usually lapsed on their own by the time a cleanup removal arrives. Re-sending a removal also lands every borrower here — the request is safe to retry. A consistently non-zero `unknown` count usually means the wrong identifier is being sent — a loan number where the enrollment used a customer id, or an identifier that changes between systems. Those borrowers stay monitored until a removal names them correctly. ## Removing versus deleting This endpoint stops monitoring and keeps the borrower's data. To also remove the borrower's stored details, use `DELETE /v1/persisted_authorizers` — the stronger action, which stops monitoring too. Payment history is kept either way as the audit trail. # Retrieve Payment Results Source: https://docs.rightfoot.com/api-reference/payments/get-payment-events GET /v1/payment_events Every payment event Rightfoot holds for one borrower, oldest first # Retrieve Payment Results Name one borrower — the `authorizer_unique_id` you [enrolled](/api-reference/payments/create-payment-enrollments) them with — and get back everything Rightfoot knows about their payments. The response is a flat list of **events**, oldest first, not a list of payments. One payment produces several events over its life, and `reference` is what tells you which payment an event belongs to. Pull on whatever cadence suits you. Nothing here expires, and re-reading is free. A borrower Rightfoot has no record of comes back as an empty `events` list, not a `404`. An empty result is therefore not confirmation that the identifier is correct — a typo looks the same as a borrower with no payments yet. ## The life of a payment A payment appears as `processing` when it goes out, and gains a second event when it resolves: | Sequence | Meaning | | ------------------------------------------ | ------------------------------------------ | | `processing` → `settled` | Collected | | `processing` → `returned` | Bounced. Never settled — see `return_code` | | `processing` → `settled` → `late_returned` | Settled, then clawed back | A borrower can have more than one payment: an original, and after a return, up to two reinitiations. Each carries a different `reference`, and their events are interleaved in one chronological list. ## Statuses | Status | Meaning | Moves money | | --------------- | ------------------------------------------------------------------------ | --------------------------- | | `processing` | Submitted to the payment processor and in flight | Not yet | | `settled` | Cleared | Yes — money received | | `returned` | Bounced, and never settled | No | | `late_returned` | Settled, then clawed back | Yes — reverses a settlement | | `invalid` | The payment processor refused it: the account details themselves are bad | No | | `voided` | Cancelled before it went out | No | | `refunded` | Returned to the borrower | Yes | | `not_attempted` | Never sent | No | `return_code` is present on `returned` and `late_returned` only, and carries the payment processor's NACHA R-code — `R01` for insufficient funds, `R02` for a closed account, and so on. ## Posting to your ledger * `settled` is a payment received against that `reference`. * `returned` and `late_returned` reverse the settlement you booked for that `reference`. An ordinary `returned` never settled in the first place, so most of the time there is nothing to reverse — a reversal with no matching credit is normal here rather than an error. * Every other status moves no money and needs no entry. Use `event_id` as your idempotency key. It is unique, stable, and never reissued, so replaying the same read is safe. Rightfoot reports only what it observed at the payment processor, and never infers an event to fill a gap. If a payment settled and was clawed back between two reads, you receive the `late_returned` with no `settled` before it — so treat a reversal without a matching settlement as expected, not as bad data. ## Amounts `amount` is an integer number of cents, and is the amount of the payment the event belongs to — `42500` is \$425.00. Every event on the same `reference` carries the same amount. # List Processor Keys Source: https://docs.rightfoot.com/api-reference/payments/list-payment-connections GET /v1/payment_connections Every key registered for your account, and which payment types have no default # List Processor Keys Every processor key registered for your account, ordered defaults first and then oldest first. The response never contains a key — only `key_last4`, so you can tell them apart. This always returns `200`. An account with no keys yet gets an empty `connections` list and both payment types in `missing_default_rails`. ## Check your default coverage `missing_default_rails` lists the payment types — `ach`, `card` — with no default key. Borrowers enrolled without a `merchant_ref` are skipped on those types until you [add](/api-reference/payments/create-payment-connection) or [promote](/api-reference/payments/update-payment-connection#make-a-key-the-default) a default. A `both` key covers both types, so the list is empty when you have one. ## Response Fields | Field | Description | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `connections` | Your keys. Each carries its `id`, `label`, `merchant_ref`, `rail_scope`, `is_default`, `key_last4`, `last_poll_at`, `access_scope` (reserved — always `lender_admins` today) and `created_at` | | `missing_default_rails` | Payment types with no default key | | `ingested` | Payments Rightfoot has read from your processor through your keys | | `matched` | Of those, the number matched to a borrower you enrolled | | `last_poll_at` | When Rightfoot last read results from any of your keys. `null` until the first read | # Processor Keys in the Portal Source: https://docs.rightfoot.com/api-reference/payments/processor-keys-portal Add, tag and manage your processor keys from the Rightfoot portal — no API call needed # Processor Keys in the Portal Everything the [key endpoints](/api-reference/payments/create-payment-connection) do is also available in the Rightfoot portal, under **Payments → Connection**. Use it when the person who holds the keys at your [payment processor](/api-reference/payments/create-payment-connection#supported-processors) is not the person wiring the integration — nothing on this page needs an API call, and the keys it saves are the same ones the API lists. The Connection page listing four processor keys with Default, tagged and Not in use badges If you don't see **Payments** in the portal sidebar, ask Rightfoot to enable it for your account. ## Add a key 1. Click **Add key** and paste the key from your processor. It is stored in a vault as soon as you save; afterwards the page shows only its last four characters. 2. **How should we use it?** — choose **Default key** for accounts you enroll without a tag, or **Tagged key** for accounts you enroll with one. A key is one or the other. 3. **Which payments can it take?** — **ACH**, **Debit card**, or **Both**. Pick what the merchant account behind the key is set up for. 4. For a tagged key, enter the **Tag**: lowercase letters, numbers, dashes or underscores. Send the same value as `merchant_ref` when you enroll those borrowers. It cannot be changed after saving. 5. Give the key a **Label**. The portal suggests one from your choices; it only appears in the portal, and you can rename it any time. The Add a payment key dialog with Tagged key and Debit card selected and a tag entered Every field in the dialog is a field on [`POST /v1/payment_connections`](/api-reference/payments/create-payment-connection): | In the portal | In the API | | ----------------------------------------------------- | ------------------------------------- | | Processor API key | `api_key` | | How should we use it? → **Default key** | `is_default: true` | | How should we use it? → **Tagged key**, plus **Tag** | `merchant_ref` | | Which payments can it take? → ACH / Debit card / Both | `rail_scope`: `ach` / `card` / `both` | | Label | `label` | ## Which keys do you need? * **Your merchant accounts are split by payment type only** — one for ACH, one for cards. Add each as a **Default key** with the matching payment type, or a single **Default key** set to **Both** if one account takes everything. Nothing extra is needed on enrollment: Rightfoot picks the key by the payment type being used. * **Your merchant accounts are split by entity, state or brand.** Add each as a **Tagged key** and send its tag as `merchant_ref` when you enroll those borrowers. You can still keep a **Default key** for anyone enrolled without a tag. * **Both.** A tag can hold one key per payment type, so `acme-nv` can have an ACH key and a debit-card key. ## Keep every payment type covered A payment type with no default key skips every borrower enrolled without a tag. The page warns you and offers two fixes: **Add a default key**, or **Choose an existing key** to promote a saved key that is not in use. A warning reading You don't have a default key for Debit card, with Add a default key and Choose an existing key buttons ## Manage a key Open the row menu (**⋯**) on any key. | Action | What it does | In the API | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | **Make default** / **Remove as default** | Makes an untagged key the default for its payment types, swapping with the current default; or stops it being the default | [`PATCH` with `is_default`](/api-reference/payments/update-payment-connection#make-a-key-the-default) | | **Edit** | Rename the key or change which payments it takes | [`PATCH` with `label` or `rail_scope`](/api-reference/payments/update-payment-connection) | | **Replace key** | Paste a new key from your processor. The old one is revoked; the tag, label and payment types stay | [`PATCH` with `api_key`](/api-reference/payments/update-payment-connection#replace-a-key) | | **Delete** | Revokes the key at the processor and removes it. Payments already collected are kept | [`DELETE`](/api-reference/payments/delete-payment-connection) | A tagged key is never offered **Make default** — a tag is a deliberate choice. To serve borrowers enrolled without a tag, add or promote an untagged key. The **Payment reference format** card at the bottom of the page is about matching, not keys: put `RF-{authorizer_id}-{your_payment_id}` in the reference of each payment you create at the processor so Rightfoot can match its result to the borrower. # Update a Processor Key Source: https://docs.rightfoot.com/api-reference/payments/update-payment-connection PATCH /v1/payment_connections/{id} Rename a key, change its payment types, make it the default, or replace the key itself # Update a Processor Key Send only the fields you want to change. At least one is required. | Field | What it does | | ------------ | ------------------------------------------------------------------------------------ | | `label` | Renames the key | | `rail_scope` | Changes which payment types it can take | | `is_default` | `true` makes it the default for its `rail_scope`; `false` stops it being the default | | `api_key` | Replaces the stored key. The previous one is revoked once the new one is stored | `merchant_ref` is not accepted — see [Tags cannot change](#tags-cannot-change). ## Make a key the default Promoting a key (`is_default: true`) swaps it with the current default for the same `rail_scope`, if there is one. That key becomes an ordinary untagged key; nothing else about it changes. Only untagged keys can be promoted. A tagged key is reached by naming its tag, and making it the default would send untagged borrowers to a merchant account you set aside for tagged ones — the request is rejected with `400`. To serve borrowers enrolled without a tag, add or promote an untagged key. Demoting (`is_default: false`) needs no replacement. A payment type the demoted key covered falls back to a default `both` key if you have one; only when no other default covers it are borrowers enrolled without a tag skipped on that type, until you set a new default — `GET /v1/payment_connections` shows any gap in `missing_default_rails`. ## Replace a key To rotate credentials, send the new `api_key` on its own. The label, tag and payment types stay as they are, `key_last4` changes, and the previous key is revoked as soon as the new one is stored. If the new key cannot be stored, the previous one stays in place. Replacing the key is also how you move a tag to a different merchant account: swap in that account's key and every borrower enrolled under the tag follows. ## Tags cannot change `merchant_ref` is fixed when the key is added. Borrowers already enrolled under a tag would silently move to a different merchant account if it could change, so a request that includes `merchant_ref` is rejected with `400`. ## When a request is rejected | Status | When | | ------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | Nothing to change, `merchant_ref` was sent, or a tagged key was promoted | | `404` | No key with this `id` for your account | | `409` | Moving a default key onto a `rail_scope` that already has a default, or moving a tagged key onto a `rail_scope` its tag already has a key for | # Retrieve Balance Results Source: https://docs.rightfoot.com/api-reference/stable/get-balances GET /v1/balances Retrieve processed balances for a batch # Retrieve Balance Results Retrieve processed balance results for a previously submitted batch. This endpoint supports pagination to handle large result sets efficiently. ## Balance Status Codes The `balance_status_code` field indicates the outcome of the balance check: | Code | Description | | ------ | --------------------------------------------- | | `0` | Success - Balance retrieved successfully | | `3000` | No tax ID match | | `3001` | No SSN match | | `3100` | No date of birth match | | `3200` | Account closed | | `3300` | Institution not supported | | `3310` | Institution currently in testing | | `3400` | No match with provided authorizer information | | `9000` | Other error | A `null` balance with a non-zero status code indicates that the balance check failed for the specified reason. ## Pagination For batches with more results than the specified limit, use pagination: ```python Python theme={null} import requests def get_all_balances(batch_id, api_key): balances = [] next_page_token = None while True: url = f"https://api.rightfoot.com/v1/balances?batchId={batch_id}&limit=100" if next_page_token: url += f"&next_page_token={next_page_token}" headers = {"Authorization": f"Bearer {api_key}"} response = requests.get(url, headers=headers) data = response.json() balances.extend(data['balances']) if not data['has_more']: break next_page_token = data['next_page_token'] return balances ``` ```javascript JavaScript theme={null} async function getAllBalances(batchId, apiKey) { let balances = []; let nextPageToken = null; while (true) { let url = `https://api.rightfoot.com/v1/balances?batchId=${batchId}&limit=100`; if (nextPageToken) { url += `&nextPageToken=${nextPageToken}`; } const response = await fetch(url, { headers: { 'Authorization': `Bearer ${apiKey}` } }); const data = await response.json(); balances = balances.concat(data.balances); if (!data.has_more) { break; } nextPageToken = data.next_page_token; } return balances; } ``` ## Best Practices 1. **Poll periodically** - If processing is not complete, poll every 30-60 seconds 2. **Handle pagination** - Always check `has_more` and use `next_page_token` for complete results 3. **Process status codes** - Check `balance_status_code` to understand failed balance checks 4. **Implement retries** - Add exponential backoff for transient errors (5xx status codes) # Retrieve Balance Results (CSV) Source: https://docs.rightfoot.com/api-reference/stable/get-balances-csv GET /v1/balances/csv Download balance results for one or more batches as a CSV file # Retrieve Balance Results (CSV) Download balance results for one or more batches as a CSV file. The CSV includes the same columns produced by the Bigfoot dashboard download (`Institution`, `Phone`, `Zip`, `Creator`) in addition to the core fields returned by [Retrieve Balance Results](/api-reference/stable/get-balances). This endpoint returns a richer field set than `GET /v1/balances`. Use it when you need the full dashboard-style export. For paginated JSON per row, use `GET /v1/balances`. ## Multiple batches in a single export Repeat the `batch_ids` query parameter to fetch multiple batches in one CSV. All batches must belong to the authenticated requester — if any do not, the request is rejected with `403` and no file is returned. ```bash cURL theme={null} curl -X GET 'https://api.rightfoot.com/v1/balances/csv?batch_ids=8ac1f4a6-1234-4abc-9d3e-001122334455&batch_ids=12c4d6ee-aaaa-4abc-9d3e-bbbbccccdddd' \ -H 'Authorization: Bearer rf_live_...' \ -o balances.csv ``` ```python Python theme={null} import requests def download_balances_csv(batch_ids, api_key, output_path="balances.csv"): url = "https://api.rightfoot.com/v1/balances/csv" params = [("batch_ids", bid) for bid in batch_ids] headers = {"Authorization": f"Bearer {api_key}"} with requests.get(url, params=params, headers=headers, stream=True) as response: response.raise_for_status() with open(output_path, "wb") as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) return output_path ``` ```javascript JavaScript theme={null} import fs from "node:fs"; async function downloadBalancesCsv(batchIds, apiKey, outputPath = "balances.csv") { const params = new URLSearchParams(); for (const id of batchIds) { params.append("batch_ids", id); } const response = await fetch( `https://api.rightfoot.com/v1/balances/csv?${params.toString()}`, { headers: { Authorization: `Bearer ${apiKey}` } } ); if (!response.ok) { throw new Error(`Request failed: ${response.status} ${response.statusText}`); } const csv = await response.text(); fs.writeFileSync(outputPath, csv); return outputPath; } ``` ## Response Returns `text/csv; charset=utf-8` with `Content-Disposition: attachment; filename="batch-export-.csv"`. The CSV contains a header row followed by one row per balance result. The first nine columns are fixed; additional `meta_` columns are appended alphabetically when any authorizer in the result set has metadata. | Column | Description | | --------------- | -------------------------------------------------------------------------------------------------------------------------- | | `Authorizer ID` | Your authorizer's unique identifier. | | `Batch ID` | The batch this result belongs to. | | `Institution` | Full name of the financial institution. | | `Phone` | Authorizer phone number, if known. | | `Zip` | Authorizer zip code, if known. | | `Balance` | Account balance in USD (decimal). Empty when unavailable. | | `Status` | One of `SUCCESS`, `NO_MATCH`, `ACCOUNT_CLOSED`, `BANK_UNSUPPORTED`, `RETRIEVAL_FAILED`, `RETRY_LIMIT`, `PROCESSING_ERROR`. | | `Creator` | Display name of the dashboard user who submitted the request. Empty for API submissions. | | `Timestamp` | ISO-8601 timestamp (UTC) when the result was recorded. | | `meta_` | One column per metadata key set on any authorizer in the result. | ### Example response ```csv theme={null} Authorizer ID,Batch ID,Institution,Phone,Zip,Balance,Status,Creator,Timestamp,meta_customer_ref authz-001,8ac1f4a6-1234-4abc-9d3e-001122334455,Bank of America,+15551234567,10001,2843.51,SUCCESS,jane.doe@acme.com,2026-05-17T19:24:12+00:00,CR-9981 authz-002,8ac1f4a6-1234-4abc-9d3e-001122334455,Wells Fargo,,90210,,NO_MATCH,,2026-05-17T19:25:03+00:00, ``` ## Errors | Status | Cause | | ------ | -------------------------------------------------------------------------- | | `400` | Missing `batch_ids` or one of the values is not a valid UUID. | | `401` | Missing or invalid API key. | | `403` | One or more requested batches don't belong to the authenticated requester. | | `500` | Internal server error. | ## Best Practices 1. **Stream to disk** - CSV exports can be large; use `-o` (cURL) or stream the response body rather than buffering it in memory 2. **Match by `Authorizer ID`** - Pair rows back to your records using the `Authorizer ID` column, which mirrors the `authorizer_unique_id` you submitted 3. **Check `Status` per row** - An empty `Balance` paired with a non-`SUCCESS` `Status` indicates the balance check failed for that authorizer # Submit Balance Check Source: https://docs.rightfoot.com/api-reference/stable/submit-balance-request POST /v1/balance_requests Submit a batch of authorizers for balance checks # Submit Balance Check Submit a batch of authorizers for balance checks. This endpoint allows you to submit up to 1,000 authorizers in a single request to retrieve their current account balances. **Debit Card Information:** Balance requests using debit card information is currently restricted to approved customers only. If you would like to learn more, please reach out to us at [sales@rightfoot.com](mailto:sales@rightfoot.com). ## Key Information
**Batch Size Limit:** Maximum 1,000 authorizers per request
**Idempotency:** Requests are cached for 24 hours based on the request body hash. Submitting an identical batch within this period will return the same response without reprocessing
## Important Notes We ask that you schedule the API call with sufficient time prior to ACH cut-offs to retrieve the balance results and omit authorizers with insufficient balances for the loan payment. ## Payment Information Types You must provide either **bank account information** or **debit card information** (approved customers only): ### Bank Account Information * Required: `account_number`, `routing_number`, and either `ssn` or `ein` ### Debit Card Information * Required: `debit_card_number`, and either `ssn` or `ein` * Optional: `cvv`, `expiration_month`, `expiration_year`, `routing_number` (if linked to a bank account) * **Note:** Debit card balance checks require prior approval # Submit Balance Check (CSV) Source: https://docs.rightfoot.com/api-reference/stable/submit-batch-csv POST /v1/balance_requests/csv Submit a batch of authorizers for balance checks via CSV upload # Submit Balance Check (CSV) Upload a CSV file of authorizers for balance checks. This endpoint is the file-upload counterpart to [Submit Balance Check](/api-reference/stable/submit-balance-request) — use it when your authorizer list is already exported as a spreadsheet. **Leading zeros are significant.** Many tools that generate CSVs treat numeric-looking columns as numbers and silently drop leading zeros, turning `021000021` into `21000021`. Configure your export so these columns are written as text. Rows where `routing_number`, `ssn`, `zip_code`, or `debit_card_number` lost a leading zero fail validation and are returned to you in `failed_authorizers`, so you will see the problem right away. **`account_number` has no length or format validation.** A truncated account number is accepted, processed, and used to contact the bank. The result comes back as a no-match, which looks like a data-quality problem rather than a formatting one. Verify that column before uploading. A 10-digit `ein` or a 4-digit `cvv` can also lose a leading zero and still pass validation. **Debit Card Information:** Balance requests using debit card information is currently restricted to approved customers only. If you would like to learn more, please reach out to us at [sales@rightfoot.com](mailto:sales@rightfoot.com). ## Key Information
**Batch Size Limit:** Maximum 5,000 rows per CSV
**Content-Type:** `multipart/form-data` with the file part as `text/csv`
**Encoding:** UTF-8 preferred. Files exported from Excel on Windows are decoded as Latin-1 with a `warnings` entry on the response.
**Validation:** Per-row validation runs synchronously at request time. Failures are returned inline in the response.
## CSV Format The header row must use the column names below. Required and optional columns differ slightly depending on whether you submit **bank-account** or **debit-card** info — provide exactly one per row, never both. ### Always Required (every row) | Column | Format | | ---------------------- | ----------------------------------------------------------------------------------- | | `authorizer_unique_id` | Alphanumeric, must be unique within the CSV | | `phone_number` | Exactly 10 digits, no separators (e.g. `5551234567`) | | `ssn` *or* `ein` | At least one. `ssn`: 9 digits or `XXX-XX-XXXX`. `ein`: 9–10 digits or `XX-XXXXXXX`. | ### Bank-Account Mode Provide `account_number`: | Column | Required? | Format | | ---------------- | --------- | ---------------------------------------------------------------------------------- | | `account_number` | required | Free-form string. Not length- or format-validated — leading zeros are significant. | | `routing_number` | required | 9 digits | ### Debit-Card Mode Provide `debit_card_number` (approved customers only): | Column | Required? | Format | | ------------------- | --------- | ------------ | | `debit_card_number` | required | 16 digits | | `cvv` | optional | 3–4 digits | | `expiration_month` | optional | Integer 1–12 | | `expiration_year` | optional | 4 digits | | `routing_number` | optional | 9 digits | Submitting both `account_number` and `debit_card_number` on the same row is rejected. Pick one mode per row. ### Always Optional (any row) | Column | Format | | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `first_name`, `last_name` | Free-form | | `street_address_line1`, `street_address_line2`, `city`, `state`, `country` | Free-form | | `zip_code` | 5 digits or `ZZZZZ-NNNN`. **Required for Discover** (routing number `031100649`). | | `date_of_birth` | `MM/DD/YYYY`. If provided, all three components must be present and year must be ≥ 1900. | | `meta_*` | Any column prefixed with `meta_` is collected into a per-row `metadata` object (the `meta_` prefix is stripped). | ## Validation Behavior Validation runs in-process at request time — no PII is staged on the batch row. * **Mixed result (some rows valid, some invalid):** the batch is created with the valid subset, and per-row failures are returned inline in `failed_authorizers`. * **All rows invalid:** the request returns `400 Bad Request` with the per-row failures in `error.details.failed_authorizers`. No batch is created. * **Empty file or wrong content type:** the request returns `400 Bad Request` before any rows are read. # Balance Check Run Completed Source: https://docs.rightfoot.com/api-reference/webhooks/balance-batch-completed WEBHOOK balance_check.run.completed Fires when a balance check run completes # Balance Check Run Completed Webhook Fires when a balance check run completes. A notification will be sent to the `webhook_url` specified in the `/v1/balance_requests` endpoint. **Important Considerations:** * Webhook delivery is not guaranteed * Webhooks will be retried for up to 30 minutes with exponential backoff * If delivery fails after 30 minutes, no further retries will occur * Always implement polling as a fallback mechanism If your webhook endpoint restricts inbound traffic by IP, make sure to allowlist Rightfoot's outbound IPs. See [IP Allowlisting](/api-reference/webhooks/ip-allowlist). ## Webhook Response Your webhook endpoint should respond with an HTTP `2xx` status code to confirm receipt: ```json theme={null} { "status": "received" } ``` If a `2xx` response is not received, the webhook will be retried with exponential backoff for up to 30 minutes. ## Implementing a Webhook Endpoint Here are examples of implementing a webhook endpoint in various languages: ```python Python (Flask) theme={null} from flask import Flask, request, jsonify import logging app = Flask(__name__) @app.route('/webhook/balance-batch-complete', methods=['POST']) def handle_balance_webhook(): try: # Parse the webhook payload data = request.get_json() # Validate required fields if not all(k in data for k in ['event_uuid', 'type', 'batch_id']): return jsonify({'error': 'Missing required fields'}), 400 # Verify the event type if data['type'] != 'BALANCE_BATCH_COMPLETED': return jsonify({'error': 'Unexpected event type'}), 400 # Log the event logging.info(f"Balance batch completed: {data['batch_id']}") # Process the completed batch (fetch results, update database, etc.) process_completed_batch(data['batch_id']) # Return success response return jsonify({'status': 'received'}), 200 except Exception as e: logging.error(f"Webhook processing error: {str(e)}") return jsonify({'error': 'Internal server error'}), 500 def process_completed_batch(batch_id): # Fetch balance results using the batch_id # Update your database # Trigger any downstream processes pass ``` ```javascript Node.js (Express) theme={null} const express = require('express'); const app = express(); app.use(express.json()); app.post('/webhook/balance-batch-complete', async (req, res) => { try { const { event_uuid, type, batch_id } = req.body; // Validate required fields if (!event_uuid || !type || !batch_id) { return res.status(400).json({ error: 'Missing required fields' }); } // Verify the event type if (type !== 'BALANCE_BATCH_COMPLETED') { return res.status(400).json({ error: 'Unexpected event type' }); } // Log the event console.log(`Balance batch completed: ${batch_id}`); // Process the completed batch await processCompletedBatch(batch_id); // Return success response res.status(200).json({ status: 'received' }); } catch (error) { console.error('Webhook processing error:', error); res.status(500).json({ error: 'Internal server error' }); } }); async function processCompletedBatch(batchId) { // Fetch balance results using the batchId // Update your database // Trigger any downstream processes } app.listen(3000, () => { console.log('Webhook server listening on port 3000'); }); ``` ```php PHP theme={null} 'Invalid JSON payload']); exit; } // Validate required fields if (!isset($data['event_uuid']) || !isset($data['type']) || !isset($data['batch_id'])) { http_response_code(400); echo json_encode(['error' => 'Missing required fields']); exit; } // Verify the event type if ($data['type'] !== 'BALANCE_BATCH_COMPLETED') { http_response_code(400); echo json_encode(['error' => 'Unexpected event type']); exit; } // Log the event error_log("Balance batch completed: " . $data['batch_id']); try { // Process the completed batch processCompletedBatch($data['batch_id']); // Return success response http_response_code(200); echo json_encode(['status' => 'received']); } catch (Exception $e) { error_log("Webhook processing error: " . $e->getMessage()); http_response_code(500); echo json_encode(['error' => 'Internal server error']); } function processCompletedBatch($batchId) { // Fetch balance results using the batchId // Update your database // Trigger any downstream processes } ?> ``` ## Retry Mechanism The webhook delivery system implements exponential backoff: 1. **Initial attempt** - Immediate 2. **First retry** - After 1 minute 3. **Second retry** - After 2 minutes 4. **Third retry** - After 4 minutes 5. **Subsequent retries** - Doubling interval up to 30 minutes total After 30 minutes, no further retry attempts will be made. ## Security Considerations To secure your webhook endpoint: 1. **Use HTTPS** - Always use SSL/TLS encryption for your webhook endpoint 2. **Validate payloads** - Check that all required fields are present 3. **Implement idempotency** - Handle duplicate webhook deliveries gracefully 4. **Add authentication** - Consider implementing webhook signatures or API keys 5. **Rate limiting** - Protect against potential abuse ## Fallback Strategy Since webhook delivery is not guaranteed, implement a polling fallback: ```python Python theme={null} import time import requests def wait_for_batch_completion(batch_id, api_key, max_wait=3600): """ Poll for batch completion with exponential backoff """ start_time = time.time() poll_interval = 30 # Start with 30 seconds while time.time() - start_time < max_wait: # Check if batch is complete response = requests.get( f"https://api.rightfoot.com/v1/balances?batchId={batch_id}&limit=1", headers={"Authorization": f"Bearer {api_key}"} ) if response.status_code == 200: # Batch is complete return True elif response.status_code == 404: # Batch still processing time.sleep(poll_interval) poll_interval = min(poll_interval * 1.5, 300) # Cap at 5 minutes else: # Handle error raise Exception(f"Error checking batch status: {response.status_code}") return False # Timeout reached ``` ```javascript JavaScript theme={null} async function waitForBatchCompletion(batchId, apiKey, maxWait = 3600) { const startTime = Date.now(); let pollInterval = 30000; // Start with 30 seconds while ((Date.now() - startTime) / 1000 < maxWait) { try { const response = await fetch( `https://api.rightfoot.com/v1/balances?batchId=${batchId}&limit=1`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); if (response.status === 200) { // Batch is complete return true; } else if (response.status === 404) { // Batch still processing await new Promise(resolve => setTimeout(resolve, pollInterval)); pollInterval = Math.min(pollInterval * 1.5, 300000); // Cap at 5 minutes } else { // Handle error throw new Error(`Error checking batch status: ${response.status}`); } } catch (error) { throw error; } } return false; // Timeout reached } ``` ## Best Practices 1. **Always implement polling** - Don't rely solely on webhooks 2. **Handle duplicates** - Your system should be idempotent 3. **Log all events** - Keep an audit trail of webhook receipts 4. **Monitor failures** - Set up alerts for webhook processing errors 5. **Process asynchronously** - Return `200` quickly and process in the background # Balance Threshold Alert Source: https://docs.rightfoot.com/api-reference/webhooks/balance-threshold-alert WEBHOOK balance_threshold_alert Webhook notification when authorizers meet or exceed configured balance thresholds # Balance Threshold Alert Webhook When a scheduled balance check finds authorizers whose balance meets or exceeds their configured threshold, a notification is sent to the `webhook` URL specified when creating the monitoring schedule. **Important Considerations:** * Webhook delivery is not guaranteed * Webhooks will be retried for up to 30 minutes with exponential backoff * If delivery fails after 30 minutes, no further retries will occur * Always implement polling as a fallback mechanism If your webhook endpoint restricts inbound traffic by IP, make sure to allowlist Rightfoot's outbound IPs. See [IP Allowlisting](/api-reference/webhooks/ip-allowlist). ## Payload Structure ```json theme={null} { "event_uuid": "7b6ff2ac-4779-4893-b5a8-d2c726d366bd", "type": "BALANCE_THRESHOLD_ALERT", "schedule_id": "6ddd3761-bb1c-4538-b278-09717a2e8bba", "batch_id": "d0b12bb3-ea9e-49fb-bd13-21be687cfc04", "alerts": [ { "authorizer_unique_id": "user-123", "institution_name": "First National Bank", "balance": 150.00, "threshold_amount": 100.00 }, { "authorizer_unique_id": "user-456", "institution_name": "Community Credit Union", "balance": 250.50, "threshold_amount": 200.00 } ], "timestamp": "2024-01-15T10:30:00Z" } ``` ## Payload Fields | Field | Type | Description | | ------------------------------- | ------ | ---------------------------------------------------------------- | | `event_uuid` | string | Unique identifier for this webhook event | | `type` | string | Always `BALANCE_THRESHOLD_ALERT` for this event type | | `schedule_id` | string | The monitoring schedule that triggered the alert | | `batch_id` | string | The batch ID for this balance check run | | `alerts` | array | List of authorizers that met or exceeded their threshold | | `alerts[].authorizer_unique_id` | string | Your unique identifier for the authorizer | | `alerts[].institution_name` | string | The name of the financial institution this authorizer is tied to | | `alerts[].balance` | number | The authorizer's current balance in dollars | | `alerts[].threshold_amount` | number | The configured threshold in dollars | | `timestamp` | string | ISO 8601 timestamp when the event was created | ## Webhook Response Your webhook endpoint should respond with an HTTP `2xx` status code to confirm receipt: ```json theme={null} { "status": "received" } ``` If a `2xx` response is not received, the webhook will be retried with exponential backoff for up to 30 minutes. ## Implementing a Webhook Endpoint Here are examples of implementing a webhook endpoint to handle threshold alerts: ```python Python (Flask) theme={null} from flask import Flask, request, jsonify import logging app = Flask(__name__) @app.route('/webhook/threshold-alert', methods=['POST']) def handle_threshold_alert(): try: # Parse the webhook payload data = request.get_json() # Validate required fields if not all(k in data for k in ['event_uuid', 'type', 'batch_id', 'alerts']): return jsonify({'error': 'Missing required fields'}), 400 # Verify the event type if data['type'] != 'BALANCE_THRESHOLD_ALERT': return jsonify({'error': 'Unexpected event type'}), 400 # Process each alert for alert in data['alerts']: logging.info( f"Threshold met: {alert['authorizer_unique_id']} " f"has ${alert['balance']} (threshold: ${alert['threshold_amount']})" ) # Take action - initiate payment, send notification, etc. process_threshold_alert(alert) # Return success response return jsonify({'status': 'received'}), 200 except Exception as e: logging.error(f"Webhook processing error: {str(e)}") return jsonify({'error': 'Internal server error'}), 500 def process_threshold_alert(alert): # Initiate payment collection # Update your database # Send notifications pass ``` ```javascript Node.js (Express) theme={null} const express = require('express'); const app = express(); app.use(express.json()); app.post('/webhook/threshold-alert', async (req, res) => { try { const { event_uuid, type, batch_id, alerts } = req.body; // Validate required fields if (!event_uuid || !type || !batch_id || !alerts) { return res.status(400).json({ error: 'Missing required fields' }); } // Verify the event type if (type !== 'BALANCE_THRESHOLD_ALERT') { return res.status(400).json({ error: 'Unexpected event type' }); } // Process each alert for (const alert of alerts) { console.log( `Threshold met: ${alert.authorizer_unique_id} ` + `has $${alert.balance} (threshold: $${alert.threshold_amount})` ); // Take action - initiate payment, send notification, etc. await processThresholdAlert(alert); } // Return success response res.status(200).json({ status: 'received' }); } catch (error) { console.error('Webhook processing error:', error); res.status(500).json({ error: 'Internal server error' }); } }); async function processThresholdAlert(alert) { // Initiate payment collection // Update your database // Send notifications } app.listen(3000, () => { console.log('Webhook server listening on port 3000'); }); ``` ```php PHP theme={null} 'Invalid JSON payload']); exit; } // Validate required fields if (!isset($data['event_uuid']) || !isset($data['type']) || !isset($data['batch_id']) || !isset($data['alerts'])) { http_response_code(400); echo json_encode(['error' => 'Missing required fields']); exit; } // Verify the event type if ($data['type'] !== 'BALANCE_THRESHOLD_ALERT') { http_response_code(400); echo json_encode(['error' => 'Unexpected event type']); exit; } try { // Process each alert foreach ($data['alerts'] as $alert) { error_log(sprintf( "Threshold met: %s has $%s (threshold: $%s)", $alert['authorizer_unique_id'], $alert['balance'], $alert['threshold_amount'] )); // Take action - initiate payment, send notification, etc. processThresholdAlert($alert); } // Return success response http_response_code(200); echo json_encode(['status' => 'received']); } catch (Exception $e) { error_log("Webhook processing error: " . $e->getMessage()); http_response_code(500); echo json_encode(['error' => 'Internal server error']); } function processThresholdAlert($alert) { // Initiate payment collection // Update your database // Send notifications } ?> ``` ## Best Practices 1. **Act on alerts promptly** - Threshold alerts indicate optimal payment timing 2. **Handle duplicates** - Your system should be idempotent 3. **Log all events** - Keep an audit trail of threshold alerts 4. **Monitor failures** - Set up alerts for webhook processing errors 5. **Process asynchronously** - Return `200` quickly and process in the background # IP Allowlisting Source: https://docs.rightfoot.com/api-reference/webhooks/ip-allowlist Static IP addresses Rightfoot uses to send webhooks If your webhook endpoint is behind a firewall or restricts inbound traffic by IP, you'll need to allowlist Rightfoot's outbound IP addresses. Otherwise, webhook deliveries will be blocked and your endpoint will not receive events. ## Webhook source IPs Rightfoot sends webhooks from the following static IP addresses. Add **all** of them to your allowlist, as we run multiple services that may originate webhook traffic: ```text theme={null} 35.254.143.17 35.223.223.146 ``` These IPs apply to all Rightfoot webhook events, including [Balance Check Run Completed](/api-reference/webhooks/balance-batch-completed) and [Balance Threshold Alert](/api-reference/webhooks/balance-threshold-alert). # Authentication Source: https://docs.rightfoot.com/authentication Learn how to authenticate with the Rightfoot API # Authentication ## BearerAuth **HTTP: BearerAuth** Authentication to the API is performed via Bearer Token Authentication. Provide your API key as the bearer token in the Authorization header. All API requests must be made over HTTPS. Calls made over plain HTTP will fail. API requests without authentication will also fail. **HTTP Authorization Scheme:** bearer ## Making Authenticated Requests Include your API key as a Bearer token in the Authorization header: ```bash cURL theme={null} curl -X POST https://api.rightfoot.com/v1/balance_requests \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "authorizers": [...] }' ``` ```python Python theme={null} import requests headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } response = requests.post( "https://api.rightfoot.com/v1/balance_requests", headers=headers, json={"authorizers": [...]} ) ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.rightfoot.com/v1/balance_requests', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ authorizers: [...] }) }); ``` ## Error Responses If authentication fails, you'll receive a 401 response: ```json theme={null} { "status": "error", "status_code": 401, "error": { "code": "UNAUTHORIZED", "message": "Invalid API key provided.", "timestamp": "2024-09-16T12:01:00Z", "suggestion": "Please check your API key." }, "batch_id": null, "documentation_url": "https://api.rightfoot.com/docs" } ``` # Introduction Source: https://docs.rightfoot.com/introduction Welcome to the Rightfoot API documentation # Rightfoot API The Rightfoot API provides balance intelligence for payment and collections workflows. It allows you to check account balances on demand, set up recurring monitoring, and receive event-driven signals when balance check runs complete. Rightfoot helps you determine when to attempt payments and/or how to take action, while integrating cleanly with your existing systems. ## API Overview The Rightfoot API enables you to: * **Run on-demand balance checks** for individual or batched authorizers * **Retrieve balance check results** * **Create recurring monitoring schedules** to systematically observe account balances over time * **Define balance thresholds** and receive alerts when accounts have sufficient funds * **Access historical balance requests** at the authorizer level to support monitoring and analysis * **Receive webhooks** when balance check runs complete or threshold conditions are met The API supports both **one-time execution** and **recurring monitoring**. ## Execution Models Rightfoot supports two primary execution models: ### On-Demand Balance Checks Use on-demand endpoints to submit balance checks immediately and retrieve results once processing completes. This is ideal for ad-hoc checks, batch uploads, or manual workflows. ### Monitoring & Threshold Alerts Use monitoring endpoints to create schedules that run balance checks automatically on a recurring cadence. Thresholds can be applied to monitoring so your systems are notified when an account's balance meets or exceeds a defined amount. This enables proactive, event-driven decisioning without requiring constant polling. ## Events & Webhooks Rightfoot emits webhooks when: * A **balance check run completes** * A **threshold condition is triggered** for a monitored account These events allow downstream systems to fetch results and take action immediately. If your endpoint sits behind a firewall, allowlist Rightfoot's webhook IPs so deliveries aren't blocked. See [IP Allowlisting](/api-reference/webhooks/ip-allowlist). ## Base URL All API requests should be made to: ```text theme={null} https://api.rightfoot.com ``` ## Authentication The Rightfoot API uses Bearer token authentication. Include your API key in the Authorization header: ```bash theme={null} Authorization: Bearer YOUR_API_KEY ``` ## Rate Limits To ensure fair usage and system stability: * Maximum batch size: **1,000 authorizers** per request * Requests are cached for **24 hours** based on request body hash ## Support For questions or assistance: * For debit card balance requests: Contact [sales@rightfoot.com](mailto:sales@rightfoot.com) * General support: [support@rightfoot.com](mailto:support@rightfoot.com) ## Getting Started Set up your API key and make your first request Submit your first balance check batch # Quickstart Source: https://docs.rightfoot.com/quickstart Get started with the Rightfoot API in minutes # Quickstart Guide This guide will walk you through submitting your first balance check request and retrieving the results. ## Prerequisites Before you begin, make sure you have: * A Rightfoot API key * A tool to make HTTP requests (cURL, Postman, or your preferred programming language) ## Step 1: Submit a Balance Check Request Submit a batch of authorizers for balance checking: ```bash cURL theme={null} curl -X POST https://api.rightfoot.com/v1/balance_requests \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "authorizers": [ { "authorizer_unique_id": "1a2b3c4d", "first_name": "John", "last_name": "Doe", "address": { "street_address_line1": "123 Main St", "city": "Anytown", "state": "CA", "zip_code": "12345" }, "phone_number": "5551234567", "date_of_birth": { "day": 1, "month": 1, "year": 1980 }, "ssn": "123456789", "account_number": "1234567890", "routing_number": "021000021" } ], "webhook_url": "https://your-api.com/webhook" }' ``` ```python Python theme={null} import requests import json url = "https://api.rightfoot.com/v1/balance_requests" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } data = { "authorizers": [ { "authorizer_unique_id": "1a2b3c4d", "first_name": "John", "last_name": "Doe", "address": { "street_address_line1": "123 Main St", "city": "Anytown", "state": "CA", "zip_code": "12345" }, "phone_number": "5551234567", "date_of_birth": { "day": 1, "month": 1, "year": 1980 }, "ssn": "123456789", "account_number": "1234567890", "routing_number": "021000021" } ], "webhook_url": "https://your-api.com/webhook" } response = requests.post(url, headers=headers, json=data) print(response.json()) ``` ### Response You'll receive a response with the batch ID: ```json theme={null} { "batch_id": "5d3c6bbb-fc1a-46a4-93da-1ce4a54b0d83", "submitted_at": "2024-09-17T10:00:00Z", "message": "Batch has been successfully submitted and is pending processing." } ``` Save the `batch_id` - you'll need it to retrieve the balance results. ## Step 2: Retrieve Balance Results After processing (typically within 1 hour), retrieve the balance results using the batch ID: ```bash cURL theme={null} curl -X GET "https://api.rightfoot.com/v1/balances?batchId=5d3c6bbb-fc1a-46a4-93da-1ce4a54b0d83" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```python Python theme={null} import requests batch_id = "5d3c6bbb-fc1a-46a4-93da-1ce4a54b0d83" url = f"https://api.rightfoot.com/v1/balances?batchId={batch_id}" headers = { "Authorization": "Bearer YOUR_API_KEY" } response = requests.get(url, headers=headers) print(response.json()) ``` ### Response ```json theme={null} { "batch_id": "5d3c6bbb-fc1a-46a4-93da-1ce4a54b0d83", "total_processed": 1, "balances": [ { "authorizer_unique_id": "1a2b3c4d", "balance": 3851.09, "timestamp": "2024-08-09T17:51:34Z", "balance_status_code": 0 } ], "has_more": false, "next_page_token": null } ``` ## Understanding Status Codes The `balance_status_code` indicates the outcome: | Code | Description | | ---- | ---------------------------------- | | 0 | Success - Balance retrieved | | 2560 | Invalid phone number | | 3000 | No tax ID match | | 3100 | No date of birth match | | 3200 | Account closed | | 3300 | Institution not supported | | 3400 | No match with provided information | | 9000 | Other error | ## Important Notes * **Batch Size Limit**: Maximum 1,000 authorizers per request * **Idempotency**: Identical requests within 24 hours return cached results ## Using Webhooks (Optional) If you provided a `webhook_url`, you'll receive a notification when processing completes: ```json theme={null} { "event_uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "type": "BALANCE_BATCH_COMPLETED", "batch_id": "5d3c6bbb-fc1a-46a4-93da-1ce4a54b0d83" } ``` Webhook delivery is attempted for up to 30 minutes with exponential backoff. Always implement polling as a fallback. ## Next Steps Now that you've successfully submitted your first balance check, explore our API endpoints: Learn about all parameters and options for balance checks Retrieve and paginate through balance results