# Physical Cards (coming soon)
Source: https://docs.confidolegal.com/docs/cards/physical-cards
This page outlines how `Physical Cards` are requested, shipped, and managed through Confido Legal.
## The Physical Card Object
```graphql theme={"system"}
type PhysicalCard {
id: string
card: Card
cardholder: Cardholder
createdOn: Date
shipment: Shipment
status: string
}
```
**Cardholder**
```graphql theme={"system"}
type Cardholder {
firstName: string
lastName: string
}
```
**Shipment**
```graphql theme={"system"}
type Shipment {
address: Address
method: 'usps' | 'fedex_priority_overnight' | 'fedex_2_day' | 'dhl_worldwide_express'
status: 'pending' | 'canceled' | 'submitted' | 'acknowledged' | 'rejected' | 'shipped' | 'returned'
tracking?: TrackingUpdate[]
}
```
## Creating a Physical Card
The `physicalCardCreate` mutation requests a new physical card. Call it with the Firm API token.
```graphql theme={"system"}
mutation PhysicalCardCreate($input: PhysicalCardCreateInput!) {
physicalCardCreate(input: $input) {
id
status
}
}
```
**Input**
* **`cardId`** — the ID of the card to issue as a physical card
* **`cardholder`** — the cardholder for the physical card
## Update a Physical Card
The `physicalCardUpdate` mutation updates an existing physical card. Call it with the Firm API token.
```graphql theme={"system"}
mutation PhysicalCardUpdate($input: PhysicalCardUpdateInput!) {
physicalCardUpdate(input: $input) {
id
status
}
}
```
**Input**
* **`id`** — the ID of the physical card to update
* **`status`** — the status of the physical card
# Virtual Cards (coming soon)
Source: https://docs.confidolegal.com/docs/cards/virtual-cards
This page outlines the `Card` object that will be used to issue and manage virtual cards through Confido Legal.
## The Card Object
```graphql theme={"system"}
type Card {
id: string
authorizationControls?: AuthorizationControls
bankAccount: BankAccount
billingAddress: Address
bin: string
cardholderName: CardholderName
client?: Client
createdOn: Date
expMonth: string
expYear: string
lastFour: string
matter?: Matter
nickname?: string
status: string
vendor?: Vendor
}
```
**AuthorizationControls**
```graphql theme={"system"}
type AuthorizationControls {
merchantAcceptorIds: AllowBlockList
merchantCategoryCode: AllowBlockList
merchantCountry: AllowBlockList
usage: UsageControls
}
type AllowBlockList {
allowed: [String]
blocked: [String]
}
type UsageControls {
category: 'single_use' | 'multi_use'
multiUse?: MultiUseControls
singleUse?: SingleUseControls
}
type MultiUseControls {
spendingLimits: SpendingLimit[]
}
type SpendingLimit {
interval: 'all_time' | 'per_transaction' | 'per_day' | 'per_week' | 'per_month'
merchantCategoryCodes?: [String]
settlementAmount: Int
}
type SingleUseControls {
settlementAmount: SingleUseSettlementAmount
}
type SingleUseSettlementAmount {
comparison: 'equals' | 'less_than_or_equals'
value: Int
}
```
## Creating a Card
The `cardCreate` mutation issues a new virtual card. Call it with the Firm API token.
```graphql theme={"system"}
mutation CardCreate($input: CardCreateInput!) {
cardCreate(input: $input) {
id
bin
cardholderName
createdOn
expMonth
expYear
lastFour
nickname
status
}
}
```
**Input**
* **`authorizationControls`** — restrictions on where and how the card can be used (nullable)
* **`bankAccountId`** — the ID of the bank account the card is funded from
* **`billingAddress`** — the billing address associated with the card, defaults to the firm address (nullable)
* **`cardholderName`** — the name of the cardholder, defaults to the firm name (nullable)
* **`clientId`** — the ID of the client to associate with the card (nullable)
* **`matterId`** — the ID of the matter to associate with the card (nullable)
* **`nickname`** — an optional nickname for the card (nullable)
* **`vendorId`** — the ID of the vendor to associate with the card (nullable)
## Update a Card
The `cardUpdate` mutation updates an existing virtual card. Call it with the Firm API token.
```graphql theme={"system"}
mutation CardUpdate($input: CardUpdateInput!) {
cardUpdate(input: $input) {
id
bin
cardholderName
createdOn
expMonth
expYear
lastFour
nickname
status
}
}
```
**Input**
* **`id`** — the ID of the card to update
* **`authorizationControls`** — restrictions on where and how the card can be used (nullable)
* **`billingAddress`** — the billing address associated with the card (nullable)
* **`clientId`** — the ID of the client to associate with the card (nullable)
* **`matterId`** — the ID of the matter to associate with the card (nullable)
* **`nickname`** — an optional nickname for the card (nullable)
* **`status`** — the status of the card (nullable)
* **`vendorId`** — the ID of the vendor to associate with the card (nullable)
## Viewing Card Details
Sensitive card details (PAN, CVV, and expiration) are never returned directly from the API. To view a card, mint a short-lived token from your server, then open Confido's hosted card page or embed it in an iframe. Card data is rendered on Confido's hosted page, so your servers stay out of PCI scope for card display.
### High level flow
### 1. Create a token
Call `cardDetailsTokenCreate` with the Firm API token and the card ID:
```graphql theme={"system"}
mutation CardDetailsTokenCreate($input: CardDetailsTokenCreateInput!) {
cardDetailsTokenCreate(input: $input) {
token
expiresAt
}
}
```
**Input**
* **`cardId`** — the ID of the card to view
**Response**
* **`token`** — a short-lived token used to access the card details page
* **`expiresAt`** — when the token expires (one hour after creation)
Keep the Firm API token on your server. Only the returned `token` should be sent to the browser.
### 2. View the card
| Environment | Base URL |
| ----------- | -------------------------------------- |
| Sandbox | `https://pay.sandbox.confidolegal.com` |
| Production | `https://pay.confidolegal.com` |
**Hosted page**
Redirect the user (or open a new tab) to the card details page with the token as a query parameter:
```
https://pay.sandbox.confidolegal.com/card?token=TOKEN
```
**iframe**
Embed the card details iframe in your application:
```html theme={"system"}
```
Register your application's parent-page origin as a [trusted domain](/docs/hosted-fields/trusted-domains) when embedding the iframe. Required in sandbox and production.
### Notes
* Tokens expire after **one hour**. When a token expires, call `cardDetailsTokenCreate` again and update the page URL or iframe `src`.
* Do not cache or log full card details. Treat the token as sensitive while it is valid.
* Your application should never collect or store PAN or CVV from the displayed card.
# Stored Disbursement Methods
Source: https://docs.confidolegal.com/docs/embedded-forms/stored-disbursement-methods
A stored disbursement method (SDM) is a saved ACH bank account or debit card that a Firm can use for later disbursements. This guide explains how to collect the payment details with a Confido-hosted iframe, then create the SDM from your server with the Firm API token.
The embed supports **manual ACH** (`ACH_DIRECT`) and **push-to-card** (`PUSH_TO_CARD`, debit cards only).
If you are integrating as a partner, use a **Firm API token** (`f_…`) for the Firm whose stored disbursement method you are creating. A Partner API token cannot mint an SDM token or create an SDM.
You can obtain a Firm API token by:
* Saving the `apiToken` returned by `createFirm`
* Exchanging a Connect code for a Firm API token
Keep the Firm API token on your server. Only the short-lived SDM token from `storedDisbursementMethodTokenCreate` may be sent to the browser.
## Prerequisites
* The Firm is `ACTIVE` and disbursements are enabled for that Firm
* You have the Firm API token for the Firm on your server
* The Firm is configured to allow the disbursement method you intend to collect
* Register your application's parent-page origin as a [trusted domain](/docs/hosted-fields/trusted-domains). Required in sandbox and production.
In sandbox, `createFirm(input: { mockOnboarding: true })` activates the Firm, enables disbursements, and returns a Firm API token you can use immediately. `mockOnboarding` is available in sandbox only.
## High level flow
## 1. Mint a token
Call `storedDisbursementMethodTokenCreate` with the Firm API token:
```graphql theme={"system"}
mutation StoredDisbursementMethodTokenCreate {
storedDisbursementMethodTokenCreate {
token
expiresAt
}
}
```
Send the returned `token` to your frontend. Mint a fresh token when the form opens. SDM tokens expire after **30 minutes**. If staging fails because the token expired, mint another and reload the iframe `src`.
## 2. Embed the form
| Environment | Base URL |
| ----------- | -------------------------------------- |
| Sandbox | `https://pay.sandbox.confidolegal.com` |
| Production | `https://pay.confidolegal.com` |
| Path | Form |
| ----------------- | --------------------- |
| `/sdm/embed` | Tabs for card and ACH |
| `/sdm/embed/card` | Push-to-card only |
| `/sdm/embed/ach` | Manual ACH only |
**Query parameters**
* **`token`** (required) — value from `storedDisbursementMethodTokenCreate`
* **`opts`** (optional) — `encodeURIComponent(JSON.stringify(branding))` where branding is `{ backgroundColor, centerColor, footerText, headerImg, headerName, partnerImg }` (`footerText` / image fields may be `null`)
The tabbed embed loads recipient ACH and instant fees from the Firm's billing settings. You do not pass fees in the URL.
```html theme={"system"}
```
A missing or invalid token, or a parent origin that is not a trusted domain, returns HTTP 403 and posts `confido:sdm_load_error`.
## 3. Submit the form from the parent
The iframe has no Save button. Listen for its events, enable your application's Save button when the form is valid, and post `confido:sdm_submit` to the payment-page origin when the user clicks Save.
Check `event.origin` against the payment-page origin. Post `confido:sdm_submit` **to that origin**, not `*`.
```javascript theme={"system"}
const PAY_ORIGIN = 'https://pay.sandbox.confidolegal.com'; // production: https://pay.confidolegal.com
const iframe = document.getElementById('sdm-embed');
const save = document.getElementById('save');
let isReady = false;
let isValid = false;
function _syncSaveButton() {
save.disabled = !(isReady && isValid);
}
window.addEventListener('message', (event) => {
if (event.origin !== PAY_ORIGIN) return;
const { type } = event.data || {};
if (type === 'confido:sdm_fields_loaded') {
isReady = true;
_syncSaveButton();
}
if (type === 'confido:sdm_height_change') {
iframe.style.height = `${Math.max(480, event.data.height)}px`;
}
if (type === 'confido:sdm_validity_change') {
isValid = event.data.isValid;
_syncSaveButton();
}
if (type === 'confido:sdm_staged') {
fetch('/your-backend/sdm/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token, // the SDM token, not the Firm API token
vendorId, // or clientId — exactly one
payeeEmail,
nickname,
}),
});
}
if (type === 'confido:sdm_stage_error' || type === 'confido:sdm_load_error') {
// Show event.data.message. If the token expired, mint a new one and reload src.
}
});
save.addEventListener('click', () => {
iframe.contentWindow.postMessage({ type: 'confido:sdm_submit' }, PAY_ORIGIN);
});
```
### Events from the iframe
Every message is `{ type, ...payload }`.
| `type` | Payload | When |
| --------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `confido:sdm_page_ready` | — | Iframe loaded |
| `confido:sdm_fields_loaded` | — | Forms are ready |
| `confido:sdm_height_change` | `{ height }` | Content height changed (pixels) |
| `confido:sdm_validity_change` | `{ isValid, method }` | Enable or disable your Save button. `method` is `ACH_DIRECT` or `PUSH_TO_CARD` |
| `confido:sdm_method_change` | `{ method }` | User switched tabs |
| `confido:sdm_bin_change` | `{ brand?, cardType? }` | Card BIN lookup result |
| `confido:sdm_field_error` | `{ field, message }` | Field-level validation error |
| `confido:sdm_field_error_cleared` | `{ field }` | That error cleared |
| `confido:sdm_staging` | — | Submit started |
| `confido:sdm_staged` | `{ method, maskedDetails }` | Safe to call `storedDisbursementMethodCreate`. `maskedDetails` includes `lastFour` and `method`; card may include `brand` / `cardType`, ACH may include `bankName` |
| `confido:sdm_stage_error` | `{ message }` | Staging failed (expired token, validation, etc.) |
| `confido:sdm_load_error` | `{ message? }` | Iframe could not load |
### Command into the iframe
| `type` | Payload | When |
| -------------------- | ------- | ----------------------------- |
| `confido:sdm_submit` | — | User clicked your Save button |
## 4. Create the stored disbursement method
After receiving `confido:sdm_staged`, send the SDM token and your non-sensitive metadata to your backend. Call `storedDisbursementMethodCreate` there with the Firm API token.
```graphql theme={"system"}
mutation StoredDisbursementMethodCreate(
$input: StoredDisbursementMethodCreateInput!
) {
storedDisbursementMethodCreate(input: $input) {
storedDisbursementMethod {
id
method
nickname
displayName
details {
lastFour
brand
bankName
routingNumber
accountHolderName
}
client {
id
}
vendor {
id
}
}
}
}
```
**Input**
* **`token`** (required) — the same SDM token used in the iframe
* **`payeeEmail`** (required) — valid email for the payee
* **`clientId`** or **`vendorId`** (required) — exactly one
* **`nickname`** (optional)
The SDM token is scoped to the Firm, and the Firm API token used for create must belong to that same Firm. The token cannot create an SDM by itself.
On success the cache entry is deleted. Creating again with the same token fails. A successful create emits [`stored_disbursement_method.created`](/docs/webhooks/webhook-types).
## Using a stored method
List methods for a client or vendor with the Firm API token:
```graphql theme={"system"}
query StoredDisbursementMethodsList($vendorId: String, $clientId: String) {
storedDisbursementMethodsList(vendorId: $vendorId, clientId: $clientId) {
total
storedDisbursementMethods {
id
method
nickname
displayName
archived
details {
lastFour
}
}
}
}
```
To send funds to a saved method, create a disbursement as usual, then call `disbursementInitiateTxnToSdm` with `{ disbursementId, sdmId }` using the Firm API token.
## Limits and errors
* **Token TTL** is 30 minutes. Remint and reload the iframe if staging reports an expiry.
* **Firm must be active.** Token create fails with `This firm is not active.`
* **Method must be allowed** on the Firm. Create fails with `{METHOD} is not allowed for this firm`.
* **Parent has no Save control inside the iframe.** If you never post `confido:sdm_submit`, details are never staged.
# Connect
Source: https://docs.confidolegal.com/docs/firm-onboarding/connect
Use Connect when your user already has an existing Confido Legal account.
For users without a Confido Legal account, use [Sign Up
Links](/docs/firm-onboarding/sign-up-links) or
[Onboarding.js](/onboarding-js/overview).
With Connect, you'll direct users to a unique Connect URL where they will authorize your application. Upon authorization, they will be redirected back to your configured Callback URL with a one-time-use `code` that can be exchanged for a Firm API Token. You'll then store this token in your database to use in subsequent requests to manage the Firm through the API.
Let's get started.
First we need to configure some settings in the Partner portal. Log in to the portal and go to Settings > Connect.
## Add your logo
It's best if this logo is square and around 200x200 pixels. This logo will show up on the Connect authorization screen as well as the Sign Up screen when users are using a Sign Up Link.
## Copy your Connect URL
Each Partner has their own unique Connect URL. You will direct your users here to start the Connect session. You can pass an optional `state` search parameter to this URL that will be threaded through the process and returned back to you at your Callback URL. This `state` parameter can be any string.
## Set up a Callback URL
The Callback URL is where we will redirect users after they have authorized your application. The GET request will include a couple search parameters.
`https://your_app.com/callback?code=connect_code&state=your_state`
You can exchange the `code` for a Firm API Token using the `exchangeCodeForFirmApiToken` mutation and your Partner API Token. You can use the `state` param for anything you like. For example, maybe you want to pass through the `userId` of the user who started the connect session.
# Firm Status
Source: https://docs.confidolegal.com/docs/firm-onboarding/firm-status
When a Firm is onboarding, the `firm.status` field will go through the following values. A `firm.updated` [webhook](/docs/webhooks/configure) will be sent out every time the status changes.
`CREATED` -> The Firm was created but there haven't been any edits to the application so far.
`APP_IN_DRAFT` -> The application for the Firm has been updated and is currently being filled out.
`APP_SUBMITTED` -> The application has been submitted and is waiting for the authorized signer to complete the agreement.
`APP_IN_REVIEW` -> Agreement has been signed and the application is under review.
`ACTIVE` -> The Firm has passed underwriting and is ready to start processing payments.
`INACTIVE` -> The Firm is no longer able to process payments.
Below is a simple query that gets the status of a Firm. See the [GraphQL Playground](https://api.sandbox.gravity-legal.com/) for the complete list of fields available on the Firm object.
```graphql theme={"system"}
query GetFirm {
firm {
status
}
}
{
"headers": {
"x-api-key": "firm_api_token"
}
}
```
When `status` is `ACTIVE` the Firm is ready to process payments.
# Overview
Source: https://docs.confidolegal.com/docs/firm-onboarding/overview
A Firm represents an actual business with an EIN number. For example, say there is a law firm called Nelson & Murdock. They have 4 physical locations in 2 different states. They share one operating account between all locations but have 2 different trust accounts, one in each state they operate. Nelson & Murdock would be represented as a Firm in Confido. Under that Firm would be 3 Bank Accounts, 1 operating, 2 trust.
Since the Confido Legal API handles financial transactions, regulations require that a certain amount of due diligence is performed before we allow businesses to use our platform. We call this process Firm Onboarding.
During Firm Onboarding, we collect financial information about the business and its owners. This information then goes through underwriting to validate the legitimacy and financial strength of the Firm.
Firms fill out the onboarding form through our [onboarding.js](/onboarding-js/overview) form or the Confido Legal UI. Once a Firm has been approved, they can start moving money.
## One Firm, multiple people
A **Firm** in Confido is the business entity. That Firm can then have multiple users (**Firm Admin** and **Firm User** roles) with their own logins; additional users are managed inside the Confido Legal app.
For integrations:
* **[Sign Up Links](/docs/firm-onboarding/sign-up-links)** attach the person who completes the link as an **admin** on the Firm you already created with `createFirm`.
* **[Connect](/docs/firm-onboarding/connect)** lets an existing Confido user authorize your app and yields a **Firm API token** you store server-side.
You should map **one legal entity in your product** (one “firm” row) to **one Confido Firm**—not one Confido Firm per attorney. Use Confido’s user roles (and your own `firm_staff` model) for staff vs owner; use **one** stored Firm API token per Confido Firm for server-to-server calls.
## Timeline
During onboarding, the `firm.status` field will go through the following values. A `firm.updated` [webhook](/docs/webhooks/configure) will be sent each time the `firm.status` changes.
`CREATED` -> The Firm was created but there haven't been any edits to the application yet.
`APP_IN_DRAFT` -> The application has been updated and is currently being filled out.
`APP_SUBMITTED` -> The application has been submitted and is going through our review process. From this stage on, the application is locked and cannot be modified.
`APP_IN_REVIEW` -> The application is currently under review.
`ACTIVE` -> The Firm has been approved and is ready to start processing payments.
`INACTIVE` -> The Firm is no longer set up to process payments.
## Developing in Sandbox
In our sandbox environment, the following methods can be used to set up firms depending on whether or not you are testing the onboarding flow.
### Skip onboarding flow
If you are utilizing the `createFirm` mutation, the `mockOnboarding` input parameter can be used **in sandbox only** to transition the Firm directly into the `ACTIVE` status with one default operating and one default trust bank account. This is useful when you are developing payment features and don't want to hassle with "onboarding" the Firm.
### Trigger via the Partner Portal
Through the Partner Portal, you can update the Firm status via the Firm settings page. Click `Firms` on the navbar and then click on the Firm you would like to edit.
### Trigger via a Firm Admin
If you have created a Firm Admin user for the Firm, you can log in as that user and update the status of the Firm through the Application or Settings page.
### Trigger via the GraphQL API
If you are looking to step through the statuses via the GraphQL API, we have created a series of `sandboxOnly` functions for the different steps of the onboarding flow.
`sandboxOnlyFillOnboardingData` -> Fills the application with mock data, including one operating and one trust bank account. This will not overwrite fields of the application that have already been filled out (which can be useful to create a Firm with more than one trust account).
`sandboxOnlySubmitOnboardingData` -> Transitions the Firm to the `APP_SUBMITTED` status. Under the hood, this will first call `sandboxOnlyFillOnboardingData` so this function can be called when the Firm status is either `CREATED` or `APP_IN_DRAFT`.
`sandboxOnlySendFirmToReview` -> Transitions the Firm to the `APP_IN_REVIEW` status. Under the hood, this will first call `sandboxOnlySubmitOnboardingData` so this function can be called when the Firm status is `CREATED`, `APP_IN_DRAFT`, or `APP_SUBMITTED`.
`sandboxOnlyActivateFirm` -> Transitions the Firm to the `ACTIVE` status. Under the hood, this will first call `sandboxOnlySubmitOnboardingData` so this function can be called when the Firm status is `CREATED`, `APP_IN_DRAFT`, `APP_SUBMITTED`, or `APP_IN_REVIEW`. It will error out if the Firm is already `ACTIVE` or `INACTIVE`.
## Production
To onboard firms in production, your team can utilize our [onboarding.js](/onboarding-js/overview) form or have a Firm user log in to the Confido Legal UI to complete the form.
### Timeline
When onboarding firms in the production environment, the typical timeline from initial submission of the Firm application to the Firm being able to accept payments is 2-3 business days. Firms without any underwriting issues that sign the required documents quickly can sometimes be onboarded in under 24 hours.
# Sign Up Links
Source: https://docs.confidolegal.com/docs/firm-onboarding/sign-up-links
Use Sign Up Links when your users do not already have an existing Confido Legal account.
With Sign Up Links, you will preemptively create a new Firm using the `createFirm` mutation and then create a unique sign up link where you will direct your user. When the user signs up using this link, they will be added as an admin to the Firm that you previously created.
For security reasons, Sign Up Links are short-lived, one-time-use links. They
are good for 20 minutes and include an `expiresAt` field for your convenience.
## Create a Firm
To get started with Sign Up Links, we will first create a Firm. Use the `createFirm` mutation to do this with your Partner API Token. Here is an example.
```graphql theme={"system"}
mutation CreateFirm($input: CreateFirmInput!) {
createFirm(input: $input) {
apiToken
signUpLink {
link
expiresAt
}
}
}
{
"input": {
"name": "My Firm Name",
},
"headers": {
"x-api-key": "your_partner_token"
}
}
```
Once the Firm has been created, save the returned Firm API Token in your database. You will use this in all subsequent requests for the Firm.
The Firm can be created at any time. For instance, you could create a Firm for
each account in your application in one bulk operation. Then later, when the
user is ready, generate a Sign Up Link using the `createFirmSignUpLink`
mutation.
If you know that you are going to immediately direct your user to the Sign Up Link after creating the Firm, then you can save yourself a network request by asking for the `signUpLink` field in your `createFirm` query.
## Navigate to the link
Now direct your user to the Sign Up Link URL. There they will sign up for a Confido Legal account and be placed as an admin under the Firm you created. Once signed in, they will be asked to fill out our payments application so we can begin underwriting their account.
Next you will want to know when the Firm has been approved to accept payments.
Head to the next section for a discussion on monitoring the Firm's status.
# Complete the Session
Source: https://docs.confidolegal.com/docs/hosted-fields/complete-session
Once the user has entered all their payment information, you are ready to submit the fields and complete the session. This is typically done in a button's `onClick` handler or a form's `onSubmit` handler.
To submit the sensitive data in the fields to Confido Legal, use the `submitFields()` method.
For a more detailed explanation of the `submitFields()` method, see the [Reference Docs](/hosted-fields-js/submit-fields).
```javascript theme={"system"}
window.confidoHostedFields.submitFields(async (err) => {
if (err) {
// There was an error with submitting the field
// data. We should pause here, and let the user
// fix the issue.
return;
}
// Field data was submitted successfully.
// Now we can finish off the payment from
// our backend.
const otherFormData = gatherOtherFormData();
const result = await fetch('/my-api/payment-session-complete', {
method: 'post',
body: JSON.stringify({
paymentToken,
...otherFormData,
}),
});
// The result from your backend
console.log(result);
});
```
You are now ready to finish the session from your server. You'll need to set up an API endpoint where you can send the token and any additional non-sensitive data that was collected in the form.
## Payment Session
Call the `paymentSessionComplete` mutation from your server when you are using a Payment Session to collect money. This is where you can set the amount to be paid and additional information.
```graphql theme={"system"}
mutation PaymentSessionComplete($input: PaymentSessionCompleteInput!) {
paymentSessionComplete(input: $input) {
status
transactions {
amountProcessed
}
}
}
```
Example mutation variables:
```typescript theme={"system"}
const variables = {
input: {
amount: 1500, // amount in cents
payerEmail,
paymentMethod, // sent from the browser, can be found on the Hosted Fields SDK state
paymentToken, // the same paymentToken you used to init the fields
},
};
```
## Save Payment Method Session
Call the `completeSavePaymentMethod` mutation from your server when you are saving a payment method for later use.
```graphql theme={"system"}
mutation CompleteSavePaymentMethod(
$input: CompleteSavePaymentMethodSessionInput!
) {
completeSavePaymentMethod(input: $input) {
paymentMethod
payerName
id
lastFour
status
type
}
}
```
Example variables:
```typescript theme={"system"}
const variables = {
input: {
payerEmail,
payerName,
payerZip,
paymentMethod, // sent from the browser
savePaymentMethodToken, // the same token you used to init the fields
},
};
```
Now you can use the `initiateSPMPayment` mutation at any time to collect a payment using this payment method.
# Create a Public Token
Source: https://docs.confidolegal.com/docs/hosted-fields/create-public-token
To get started, you will need to create a public token from your server. You will then pass this token to your frontend to initialize the SDK.
## Payment Sessions
Create a Payment Session Token when you want to accept money now.
You can pass a `bankAccountId` to this mutation which determines which of the Firm's Bank Accounts the money will be directed to. If you do not pass a `bankAccountId` the Firm's default operating account will be used.
```graphql theme={"system"}
mutation PaymentSessionCreate($input: PaymentSessionCreateInput) {
paymentSessionCreate(input: $input) {
paymentSessionToken
}
}
```
## Save Payment Method Session
Create a Save Payment Method Session Token when you want to save the payment details now, then use them to make a payment at a later date. You can optionally pass in a `clientId` if you know which Client this Payment Method should be saved under.
```graphql theme={"system"}
mutation StartSavePaymentMethodSession(
$input: CreateSavePaymentMethodTokenInput
) {
createSavePaymentMethodToken(input: $input) {
savePaymentMethodToken
}
}
```
You'll need to add the Firm API Token as the `x-api-key` header for the
request. `x-api-key=f_secret_env_******`
Return the `paymentToken` to your frontend to initialize the SDK.
# Initialize the SDK
Source: https://docs.confidolegal.com/docs/hosted-fields/initialize-sdk
## Load the script
Include the JavaScript SDK on each page of your site where your payment form exists. It should always be loaded directly with a `script` tag rather than included in a bundle.
```html theme={"system"}
// Or in the Sandbox Environment
```
The script will use `window.postMessage` to post a message when it has finished loading and is ready for initialization. You can set up a listener to get notified.
```typescript theme={"system"}
const myListener = (e) => {
if (e.data === 'confido_hosted_fields_script_loaded') {
console.log('sdk is loaded!');
}
};
window.addEventListener('message', myListener);
```
Then initialize the SDK with the Payment Token. For a more detailed explanation of the `init()` method, see the [Hosted Fields SDK Reference Docs](/hosted-fields-js/initialize).
```javascript theme={"system"}
// Optional CSS styles to customize the
// look of the fields
const fieldStyles = {
border: 'none',
color: 'rgb(26, 32, 44)',
'font-weight': 400,
height: '38px',
width: '100%',
'font-size': '16px',
};
window.confidoHostedFields.init({
paymentToken,
activeForm: 'card',
fields: {
accountNumber: {
containerId: 'account-number',
style: fieldStyles,
},
accountHolderName: {
containerId: 'account-holder-name',
style: fieldStyles,
},
routingNumber: {
containerId: 'routing-number',
style: fieldStyles,
},
cardNumber: {
containerId: 'card-number',
style: fieldStyles,
},
cardExpirationDate: {
containerId: 'card-exp',
style: fieldStyles,
},
cardSecurityCode: {
containerId: 'card-cvv',
style: fieldStyles,
},
},
});
```
# Listen For Changes
Source: https://docs.confidolegal.com/docs/hosted-fields/listen-for-changes
Before you initialize the SDK you will want to add a change event listener to get notified when the internal state of the fields changes.
```javascript theme={"system"}
const myListener = (changeEvent) => {
console.log(`Change Type: `, changeEvent.type);
console.log(`Current State: `, changeEvent.state);
updateMyUI(changeEvent.state);
};
window.confidoHostedFields.addChangeListener(myListener);
```
For a full list of Change Event types and a complete description of the `state` object, see the [Hosted Fields SDK Reference Docs](/hosted-fields-js/change-events).
# Overview
Source: https://docs.confidolegal.com/docs/hosted-fields/overview
With Hosted Fields, you get complete control over the payment experience for your users but stay out of PCI scope. You can accept payments directly from your application and have 100% control over the branding and user experience.
This control does come with some trade-offs:
* You will have to write more code when compared to using our payment pages.
* You will need to handle each payment method and keep your implementation up to date as we release new payment methods (e.g., Buy Now, Pay Later, Apple Pay, crypto).
With Hosted Fields, you add `div`s to your page with unique `id`s and the Confido Legal SDK will inject iframes there to collect sensitive data, keeping your code out of PCI scope.
Using Hosted Fields does require more development work on your end. We encourage all our partners to at least consider our [hosted payment pages](/docs/payment-pages/using-in-an-iframe) before making the decision to integrate with Hosted Fields. That being said, if Hosted Fields is right for you, we've made every attempt to make the developer experience as delightful as possible.
Here are the high-level steps:

1. Start a Payment Session from your server by calling the `paymentSessionCreate`
or `createSavePaymentMethodToken` mutation.
2. Pass the Public Token to your frontend.
3. Add elements on the page where you want the fields injected.
4. Load the Confido Legal Hosted Fields SDK.
5. Initialize the SDK with the Token.
6. *iFrames are injected by SDK.*
7. *User fills out the form.*
8. When user has finished and indicates they are ready to pay (usually by clicking a button) call the `sdk.submitFields()` function. This sends the sensitive field data directly to our servers.
9. After successfully submitting the sensitive data, pass additional non-sensitive data to your server and complete the payment from your backend by calling the `paymentSessionComplete` or `completeSavePaymentMethod` mutation.
10. Record the result and display it to your user.
# Trusted Domains
Source: https://docs.confidolegal.com/docs/hosted-fields/trusted-domains
Before you can load the fields on your page you must register the domain where you will be loading the fields. You can register your domains through the Partner Portal.
You should register all domains where you plan to load the fields.
To make development easier, the sandbox environment does not restrict any
domains for hosted fields. You can skip this step in sandbox.
Stored disbursement method forms do require correctly configured trusted domains.
1. Log in to the [Partner Portal](https://app.confidolegal.com)
2. Navigate to **[Settings > Trusted Domains](https://app.confidolegal.com/settings)**
3. Add your domains. You can use a wildcard like `https://*.myapp.com`. Non-local domains must use `https` protocol. `http` is acceptable for `localhost` domains, although these should only be used for testing purposes.
4. Save the domains. This could take a couple of minutes to take effect.
Now you are ready to start a session.
# API Limits & Throttling
Source: https://docs.confidolegal.com/docs/introduction/api-limits
To ensure optimal performance and fair usage, we enforce the following usage limits and throttling policies.
### Usage limits
* **500 requests per minute** *(per firmId or partnerId)* — a request authenticated with a **Firm API token** is counted against that Firm’s `firmId`. A request authenticated with a **Partner API token** is counted against the Partner’s `partnerId`. The two buckets are separate.
* **10 concurrent requests** *(per firmId or partnerId)*
### Throttling
Requests made beyond the limits mentioned above will result in a `429 Too Many Requests` response. When this happens, wait at least **60 seconds** before retrying; for bursts or migration-style workloads, use **exponential backoff** (for example retry after 60s, then 120s, capped at a few minutes) so you do not hammer the limiter.
For large backfills, spread work over time or coordinate with **[support@confidolegal.com](mailto:support@confidolegal.com)** if you expect sustained traffic above these limits.
### Bulk disbursements
The [`disbursementsCreateBulk`](/docs/send-money/bulk-disbursements) mutation runs multiple creates in one request but still counts toward the same per-firm limits and can return `429` if the Firm is already near the cap.
# Authorization
Source: https://docs.confidolegal.com/docs/introduction/authorization
There are multiple token types that you will encounter. This page covers when each token should be used, how to acquire them, and other information related to authorization.
The types of API tokens are:
* Partner Token
* Firm Token
* Payment Session Token
* Onboarding Token
To use an API token, add it to your request as the `x-api-key` header.
Our API tokens are prefixed with some human-readable information. For example,
a Partner Token may start with `p_secret_sandbox_`. The `p` indicates it's a
Partner Token, `secret` indicates it's a secret and should be treated as one,
and `sandbox` indicates that this is for the sandbox environment.
### Partner Token
Partner tokens are generated through the Confido Legal partner portal under **settings**.
If you do not have a partner account, please reach out to and we will get you set up.
Partner tokens should never be shared with the frontend. Keep them secured on
your server.
The primary use of a Partner Token is to create Firms and Firm Tokens. Below is an example partner token.
```
p_secret_sandbox_85f5a425**************afb6b1a6
```
### Firm Token
Firm tokens are generated with the `createFirmApiToken` mutation or the `createFirm` mutation. This request can be made using a Partner API Token.
Firm tokens should never be shared with the frontend. Keep them secured on
your server.
Firm tokens are used for most requests to the Confido Legal API. Some common examples are `addPaymentLink`, `addClient`, and `addMatter`. They are also used to create Payment Session Tokens for [hosted fields](/docs/hosted-fields/starting-a-payment-session). Below is an example firm token.
```
f_secret_sandbox_92828393***************assdj3
```
#### Multiple firm tokens and lifecycle
You can create more than one Firm API token for the same Firm (for example via `createFirmApiToken`, or when each user completes [Connect](/docs/firm-onboarding/connect)). Tokens are long-lived: they do not expire on a TTL. There is no refresh or rotate endpoint—create a new token when you need to rotate credentials, then revoke the old token in the Confido Legal app under **Settings → API Tokens** (or stop using it). Revoking a token causes the API to reject it immediately.
Firm API tokens are not scoped (e.g. there is no read-only vs full-access token type). Any valid Firm API token can perform the same Firm-level API operations the product allows for server-side integration.
We do **not** send a webhook when a Firm API token is created or revoked. If a token stops working, handle `401`/`403` responses by checking whether the token was revoked in the portal.
Sandbox and production use different tokens. Partner and Firm tokens are tied
to the environment where they were issued (see the `sandbox` segment in the
token prefix).
### Payment Session Token
Payment Session Tokens are generated with the `createPaymentToken` mutation. This request can only be made with a Firm API token.
Payment Session Tokens are a one-time-use token that should be used to initialize the Confido Legal JavaScript SDK on your frontend. These tokens are meant to be shared with your frontend code.
```
pay_public_sandbox_31319asd**********123alskdow
```
For more about Payment Sessions see the [hosted fields](/docs/hosted-fields/overview) section.
### Onboarding Token
Onboarding Tokens are generated with the `createOnboardingToken` mutation. This request can only be made with a Firm API token.
Onboarding Tokens are short-term tokens that should be used to initialize the Confido Legal JavaScript SDK on your frontend. These tokens are meant to be shared with your frontend code.
```
onboarding_public_sandbox_31319asd**********123alskdow
```
For more about Onboarding see the [hosted fields](/docs/firm-onboarding/overview) section.
# Overview
Source: https://docs.confidolegal.com/docs/introduction/overview
The Confido Legal API is structured as a GraphQL API. The API can be reached at the following endpoints.
* Sandbox: [https://api.sandbox.gravity-legal.com/](https://api.sandbox.gravity-legal.com/)
* Production: [https://api.gravity-legal.com/](https://api.gravity-legal.com/)
If you want to test out and run some queries and mutations, you can visit our API playground [here](https://studio.apollographql.com/graph/Confido-Legal-vqze3p/variant/current/explorer).
### Get a Partner Account
At Confido, a Partner represents another software that is integrating with our API. This could be a legal practice management software, a CRM, or any other application that needs to interact with the Confido Legal API.
Each Partner can have many Firms under it. The Partner has access to manage these Firms.
To get started with the Confido Legal API, you will need to set up a Partner account. This will allow you to access the API and make requests on behalf of your application.
Please email to get a Partner account set up.
### New to GraphQL?
* [An overview video on GraphQL](https://www.youtube.com/watch?v=eIQh02xuVw4)
* [An article on making a request to a GraphQL API](https://www.apollographql.com/blog/graphql/examples/4-simple-ways-to-call-a-graphql-api/)
# Sandbox Environment
Source: https://docs.confidolegal.com/docs/introduction/sandbox-environment
To facilitate seamless integration and thorough testing of your application, we provide a dedicated sandbox environment. Our sandbox environment is a developer-friendly space that enables you to experiment, validate, and fine-tune your integration with our payments platform without any real financial transactions taking place.
The sandbox environment is separate from your production account. You will need to sign up for a separate sandbox account to access this environment.
You can log in here:
app.sandbox.confidolegal.com
Sandbox GraphQL Endpoint:
api.sandbox.gravity-legal.com/v2
### Parity with production
The **GraphQL schema and endpoints** are the same shape in sandbox and production, but behavior differs where regulations or processors require it: for example, money movement is simulated in sandbox, and helpers such as `mockOnboarding` on `createFirm` and the `sandboxOnly*` mutations **only work in sandbox** (they are rejected in production).
### Test card and ACH data
Use the numbers on [Test payment methods](/docs/sandbox/test-payment-methods) for cards and ACH in sandbox.
### Production cutover checklist
* Use **production** Partner and Firm API tokens and webhook secret from the production Partner Portal (`app.confidolegal.com`), not sandbox values.
* Register **production** [Connect callback URL](/docs/firm-onboarding/connect) and [webhook URLs](/docs/webhooks/configure) in the production portal.
* Confirm outbound IPs or allowlisting requirements with **[support@confidolegal.com](mailto:support@confidolegal.com)** if your security team needs them; they are not documented as a universal requirement for all partners.
# Credit/Debit Card
Source: https://docs.confidolegal.com/docs/payment-methods/credit-debit-card
### Card Processing
The Credit/Debit Card Payment Method allows you to present the option for clients to make payments by entering card data, either into hosted fields or on our hosted payment pages.
***
For more details on Credit/Debit Card payments, see these knowledge articles
# Manual Payments
Source: https://docs.confidolegal.com/docs/payment-methods/manual-payments
### Recording offline payments
In some situations, a firm may be paid offline, such as by check, wire transfer or cash. When these events occur, a Manual Payment can be recorded to account for this payment. Manual Payments don't actually move money; they are purely for recordkeeping.
### Payment Link
To record manual payments on a Payment Link in Confido Legal, the `recordManualPaymentOnPaymentLink` mutation can be used. The Payment Link outstanding amounts will also be updated, resulting in the Payment Link status being `paid` or `partially_paid` depending on the total outstanding amount following the payment.
***
For more details on Manual Payments, see these knowledge articles
# Same-Day ACH
Source: https://docs.confidolegal.com/docs/payment-methods/same-day-ach
### ACH Processing
The ACH Payment Method allows you to present the option for clients to make payments by entering their bank account information, either into hosted fields or on our hosted payment pages.
***
For more details on ACH payments, see these knowledge articles
# ACH Returns
Source: https://docs.confidolegal.com/docs/payments-lifecycle/ach-returns
Sometimes ACH payments get returned after they have been settled. When this happens the funds, that may have already hit the Firm's bank account, are reversed. Partial returns do not exist.
You can get notified when an ACH payment is returned by configuring the `transaction.ach_returned` webhook. See [webhooks](/docs/webhooks/configure) for more info.
### Testing
To test ACH returns, you can call the `sandboxOnlyTriggerAchReturn` on a Transaction with `type: achPayment`. This will simulate an `Insufficient Funds` return error code and send out a webhook.
# Overview
Source: https://docs.confidolegal.com/docs/payments-lifecycle/overview
Confido Transaction Status Flow Diagram
## Statuses
`PENDING` -> The transaction was successfully authorized and has been added to the current batch to be processed. No funds have been moved yet.
`FUNDS_IN_TRANSIT` -> The transaction has been batched and is in the process of getting deposited into the firm's bank account. The amount of time the transaction is in the funds in transit status is based on the type of transaction.
`DEPOSITED` -> The transaction has been deposited into the firm's bank account.
`VOIDED` -> An authorized transaction was voided. This means it was pulled out of the batch and will not be processed. No funds will be transferred.
`RETURNED` -> The transaction, which was previously either FUNDS\_IN\_TRANSIT or DEPOSITED was returned. This can only occur with ACH transactions.
`REFUNDED` -> The transaction was fully refunded.
`PARTIALLY_REFUNDED` -> The transaction was partially refunded.
`ERROR` -> The transaction was not authorized successfully. No funds will move and this is the final status for the transaction. Error transactions can be helpful to look at while debugging issues clients are having while paying.
## Webhooks
The webhooks that are triggered when the transaction status changes can be found [here](/docs/webhooks/types).
# Voids and Refunds
Source: https://docs.confidolegal.com/docs/payments-lifecycle/voids-and-refunds
### Voids
After a successful payment has been made, there is a short period in which it can be voided using the `transactionVoid` mutation. This period depends on the firm settings and payment method used when processing the payment.
| Payment Method | Cutoff Time (Central Time) | *Legacy Cutoff Time (Central Time)* |
| -------------- | -------------------------- | ----------------------------------- |
| Card Payment | 11:00 PM | *10:30 PM* |
| ACH Payment | 11:00 PM | *12:30 PM & 9:00 PM* |
The `Transaction` object has a `canVoid` boolean that indicates whether or not the transaction can be voided.
#### Void Details
Before voiding a transaction, you can use the `transactionVoidDetails` mutation to see which transactions will be affected. This is particularly useful because a single payment may create multiple transactions, and voiding requires canceling all related transactions together.
Payments on an [Aggregate Payment
Link](/docs/payment-vehicles/aggregate-payment-links) can't be voided.
### Refunds
After a successful payment has settled, the transaction can be refunded at any time using the `transactionRefund` mutation. This mutation also contains an optional `amount` input that can be defined to make a partial refund for a specific transaction. An error will be thrown if you attempt to refund more than the original amount of the transaction.
The `Transaction` object has a `canRefund` boolean that indicates whether or not the transaction can be refunded. This is based on the expected settlement time of the transaction.
### Asynchronous Processing
Refund and void requests now support asynchronous processing to accommodate varying financial institution response times:
1. **Immediate Response:** We wait up to 30 seconds for a response from the financial institution.
2. **Pending Status:** If the request is still processing, we return a response with status `AWAITING_RESULT`
3. **Completion Handling:**
* If completed within 30 seconds: You receive the original and new refund or void transactions
* If still pending: You receive a refund or void request in the `AWAITING_RESULT` status
4. **Status Updates:** Webhooks notify you when transaction statuses change to refunded or voided
5. **Request Tracking:** The `transaction` query includes a `refundRequests` and `voidRequests` field showing status and date for each refund request. The `status_v2` field will also correctly reflect refunds or voids.
### Void or Refund
For convenience, we have also added a `transactionVoidOrRefund` mutation. However, we do not allow partial voids/refunds when using this mutation.
### Surcharging
When voiding or refunding transactions that include surcharges, the system automatically handles the surcharge amounts appropriately:
* **Voids**: Any surcharge amounts applied to the original transaction are automatically voided along with the base transaction amount
* **Refunds**: Surcharge amounts are automatically refunded proportionally based on the refund amount
### Notes
It is possible for both `canVoid` and `canRefund` to be false. This may occur when the money is in transit.
# Aggregate Payment Links
Source: https://docs.confidolegal.com/docs/receive-money/aggregate-payment-links
### Hosted payment pages for full balance payments
Aggregate Payment Links are used in situations where a client has multiple invoices or trust deposit requests outstanding, and you want to allow the client to pay off their entire balance with one payment.
If you are using Payment Links, you likely have a Payment Link for each outstanding invoice or trust request. To generate an Aggregate Payment Link, simply pass through all of the "children" Payment Links, and you will receive an Aggregate Link with an outstanding balance equal to the sum of all the children.
When a client makes a payment on an Aggregate Link, this will mark all of the children as paid. Note that partial payments are not allowed on Aggregate Payment Links, so there is no need to set any rules about how partial payments should be applied to the children.
# Payment Intents
Source: https://docs.confidolegal.com/docs/receive-money/payment-intents
If you are coming from a system that utilizes payment intents, the Confido solution is our [hosted fields](/docs/hosted-fields/overview). With this process, you generate Payment Session tokens that are used to load the sensitive information fields directly into elements in the DOM on your web page.
# Payment Links
Source: https://docs.confidolegal.com/docs/receive-money/payment-links
Payment Links are hosted payment pages of a specified amount that are tied to a specific Client. Payment Links can be presented to the Client as standalone links or embedded into your application via an iframe.
Payment Links are the fastest way to go to market. They allow you to leverage all of our pre-built Payment Methods and other features, such as surcharging.
Payment Links also allow you to benefit from new Payment Methods we add in the future, such as client financing and Apple Pay, without additional engineering work.
### PCI and hosted pages
Card data is collected on **Confido-hosted** payment pages (or in Confido-hosted iframes when you embed a link). Your servers never handle raw card numbers, which is why Payment Links are a common way to stay out of PCI scope for card entry. Your integration should still follow general security practices (TLS, protecting API tokens, etc.).
### Trust vs operating splits
Before creating or sending a Payment Link with a **trust** or **operating** split, confirm the Firm has the right **default deposit accounts** and bank accounts. Use the `bankAccountsList` / `bankAccount` queries (see the GraphQL playground) to verify `accountCategory` (`trust` vs `operating`) and IDs. If you specify a **trust** amount but the Firm has no usable default trust account, account resolution can fail or produce invalid configuration—validate in your integration rather than assuming every `ACTIVE` Firm has both categories in every edge case.
### Surcharging
Surcharging is controlled at the **Firm** level (whether surcharging is allowed and defaults) and can be overridden per Payment Link via the `surchargeEnabled` (or equivalent) field on create/update mutations where exposed. Card-brand rules and **state laws** on credit-card surcharges change over time; firms should enable surcharging only where permitted. Product-level toggles live in the Confido Legal app; partners can also pass through link-level flags where the API allows. When in doubt, direct the Firm to Confido support or counsel for compliance.
***
For more details on Payment Links, see these knowledge articles
# Stored Payment Methods
Source: https://docs.confidolegal.com/docs/receive-money/stored-payment-methods
### Overview
This payment vehicle, sometimes shortened to SPM, is used to save any of the Payment Methods to be charged at a later date. This vehicle is great for scenarios in which firms charge the same Client multiple times, for example on subscription legal services plans, payment plans, or auto-pay.
### Creation
There are two ways a stored payment method can be created.
* Collect the Payment Method information directly from a hosted Stored Payment Method link
* Save the payment method used on a different payment vehicle, such as Payment Links
### Declines
When a decline occurs on a charge towards a Stored Payment Method, the `status` of the SPM changes to `payment_failed` and the SPM will not allow more payments until the SPM is reactivated.
The ability to reactivate a SPM is based on the reason for the decline. For example, if the decline message is *Insufficient Funds*, the SPM can be reactivated using the `reactivateStoredPaymentMethod` mutation. You can determine if a SPM can be reactivated with the `canReactivate` property. You can also grab information about the decline to display to the firm with the `declineInfo` property. This can help the firm identify possible fixes before attempting to charge the SPM again.
One example of an SPM failing without being able to reactivate would be if the `declineInfo.declineMessage` was *Invalid account number*. This could occur if the client mistyped their account number when saving their ACH information. In this state, it may make sense to deactivate the SPM using the `deactivateStoredPaymentMethod` mutation.
***
For more details on Stored Payment Methods, see these knowledge articles
# Subscriptions
Source: https://docs.confidolegal.com/docs/receive-money/subscriptions
### Leverage our scheduler to run Stored Payment Methods
Think of Subscriptions as simply a scheduler that will run Stored Payment Methods on a specified schedule until specified criteria have been met.
When implementing subscription legal services or a payment plan feature, you should weigh the pros and cons of building your own scheduler and using Stored Payment Methods or using this scheduler.
The downside to using this scheduler is a lack of full control over the experience. The benefits include leveraging our set of pre-built scheduling intervals, automatic upcoming payment reminders, and logic around failed payments.
### Creation
There are two ways to create and activate a Subscription. The first is to build your subscription and attach a previously stored Stored Payment Method. The second is to generate a hosted Subscription Link on which the client can enter their payment information and automatically activate the Subscription.
***
For more details on Subscriptions, see these knowledge articles
# Test Payment Methods
Source: https://docs.confidolegal.com/docs/sandbox/test-payment-methods
## Credit and Debit Cards
| Number | Brand | Result | Type | Description |
| ------------------- | ---------------- | -------------------- | ------ | ----------------------------------------------- |
| 4242 4242 4242 4242 | Visa | success | Credit | |
| 4000 0566 5566 5556 | Visa | success | Debit | |
| 3411 11111 111111 | American Express | success | Credit | |
| 6011 0009 9013 9424 | Discover | success | Credit | |
| 3530 1113 3330 0000 | JCB | success | Credit | |
| 2720 9974 6560 4033 | Mastercard | success | Credit | Takes at least 45 seconds to return |
| 4000 3000 1111 2220 | Visa | failure | Credit | |
| 5334 3542 1767 1459 | Visa | failure | Credit | Causes a field error on the `card-number` field |
| 4000 1013 1111 2229 | Visa | failure | Credit | Takes at least 45 seconds to fail |
| 4000 1000 0000 0000 | Visa | failure | Credit | Fails when amount is over \$100.00 |
| 5555 4444 3333 2226 | Mastercard | failure | Debit | Fails when amount is over \$100.00 |
| 4005 5717 0222 2222 | Visa | failure | Credit | Fails on a tokenized payment |
## ACH
| Account Number | Routing Number | Result | Description |
| -------------- | -------------- | -------------------- | ----------- |
| \[any] | \[any] | success | |
| \[any] | 000000000 | failure | |
| 0000000000 | \[any] | failure | |
# Bulk Create Disbursements
Source: https://docs.confidolegal.com/docs/send-money/bulk-disbursements
To create multiple disbursements at once, use the bulk creation mutation. This mutation processes all disbursements synchronously and returns the results immediately.
Each disbursement created will trigger a `disbursement.created` webhook event.
## Create Multiple Disbursements
Use the following mutation to create multiple disbursements:
```graphql theme={"system"}
mutation DisbursementsCreateBulk($createInputs: [DisbursementCreateInput!]!) {
disbursementsCreateBulk(createInputs: $createInputs) {
disbursements {
id
amount
expiresAt
publicKey
status
url
}
}
}
```
**Example variables:**
```json theme={"system"}
{
"createInputs": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"amount": 100,
"authorizedIdentities": [
{
"type": "EMAIL",
"value": "myemail@example.com"
}
],
"fundingAccountId": "the_bank_account_id"
}
]
}
```
**Example response:**
```json theme={"system"}
{
"data": {
"disbursementsCreateBulk": {
"disbursements": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"amount": 100,
"expiresAt": "2024-12-31T23:59:59Z",
"publicKey": "abc123xyz",
"status": "DRAFT",
"url": "https://pay.confidolegal.com/d/abc123xyz"
}
]
}
}
}
```
You can provide a client-generated id for each disbursement in the
createInputs. If you provide an id, it must be a valid UUID v4. This id will
become the disbursement.id, allowing you to associate results with records in
your own system for better traceability.
## Response Fields
The mutation returns a `DisbursementsCreateBulkResult` object containing:
* **disbursements**: An array of `DisbursementsCreateBulkResultItem` objects, each containing:
* **id**: The disbursement ID
* **amount**: The disbursement amount
* **expiresAt**: When the disbursement expires (nullable)
* **publicKey**: The public key for the disbursement (nullable)
* **status**: The current status of the disbursement
* **url**: The URL where the disbursement can be accessed (nullable)
## Additional notes
* Each successful disbursement will emit a `disbursement.created` webhook event.
* The mutation processes all disbursements synchronously and returns immediately with the results.
# Create a Disbursement
Source: https://docs.confidolegal.com/docs/send-money/create-disbursement
The `disbursementCreate` mutation allows you to create a single disbursement. You can create a disbursement in either `DRAFT` or `APPROVED` status. The disbursement will only receive a `url` once it reaches `APPROVED` status.
## Mutation
```graphql theme={"system"}
mutation DisbursementCreate($input: DisbursementCreateInput!) {
disbursementCreate(input: $input) {
id
amount
status
url
authorizedIdentities {
type
value
}
client {
id
clientName
}
matter {
id
name
}
fundingAccount {
id
}
}
}
```
## Input Fields
### Required Fields
**amount** (Int)
The amount in cents to disburse to the client. Must be greater than 0 and less than or equal to 100000000 (i.e., greater than \$0.00 and less than or equal to \$1,000,000.00)
**authorizedIdentities** (Array of DisbursementIdentityInput)
The identities (email or phone) that are authorized to access and accept the disbursement. At least one identity is required. Each identity includes:
* **type** (DisbursementIdentityType): Either `EMAIL` or `PHONE`
* **value** (String): The email address or phone number
* **source** (DisbursementIdentitySource, optional): For informational purposes only. Used by Confido UI when identity comes from a known source, like a client.
**fundingAccountId** (String)
The ID of the bank account to pull the funds from for the disbursement.
### Optional Fields
**id** (String, nullable)
Optional client-supplied ID for the new disbursement. **Must be a valid UUID v4 string** if provided. If null, the ID will be generated automatically.
**status** (DisbursementStatus, nullable)
The initial status of the disbursement. Defaults to `DRAFT` if not specified.
* **DRAFT**: The disbursement is created but not yet approved. No `url` will be available. The disbursement must be approved before it can be accessed by the client.
* **APPROVED**: The disbursement is immediately approved and will have a `url` available. Only `FIRM_ADMIN` users can create disbursements in `APPROVED` status.
**allowedMethods** (Array of DisbursementMethod, nullable)
Specifies which payment methods are allowed for this disbursement. If left blank, the disbursement will use the firm's default allowed methods. Available methods:
* `ACH`: Slow ACH transaction via Plaid or manual entry of account and routing number
* `PUSH_TO_CARD`: Instant transaction to a Visa or Mastercard debit card
* `REQUEST_CHECK`: Firm will be informed that the client wants a check sent to them
**clientId** (String, nullable)
The ID of an existing client to associate with the disbursement. Should not be used together with `clientUpsert`.
**clientUpsert** (ClientUpsertInput, nullable)
Creates a new client or links an existing one to the disbursement based on the provided ID. This is useful when you want to automatically create a client if they don't exist, or link an existing client. Should not be used together with `clientId`. Fields:
* **id** (String): A UUID v4. This ID is first used to lookup the client. If no client is found, it will be used as the ID for a new client.
* **clientName** (String): The name of the client
* **email** (String, nullable): Client's email address
* **firstName** (String, nullable): Client's first name
* **lastName** (String, nullable): Client's last name
* **phone** (String, nullable): Client's phone number
**matterId** (String, nullable)
The ID of an existing matter to associate with the disbursement. Should not be used together with `matterUpsert`.
**matterUpsert** (MatterUpsertInput, nullable)
Creates a new matter or links an existing one to the disbursement based on the provided ID. This is useful when you want to automatically create a matter if it doesn't exist, or link an existing matter. Should not be used together with `matterId`. Fields:
* **id** (String): A UUID v4. This ID is first used to lookup the matter. If no matter is found, it will be used as the ID for a new matter.
* **clientId** (String): The ID of the client this matter belongs to
* **name** (String): The name of the matter
**enhancedSecurity** (Boolean, nullable)
If `true`, enables enhanced security for the disbursement. If `null`, uses the firm's default enhanced security settings.
**expiryDays** (Int, nullable)
The number of days until the disbursement expires. If `null`, uses the firm's default expiry days setting.
**metadata** (JSON, nullable)
A flexible JSON object that allows you to store arbitrary data. When a transaction is created for the disbursement, this metadata object is copied and saved on the transaction. This lets you associate transactions with external entities and pass through custom data as needed.
For example, you could create a disbursement with:
```json theme={"system"}
{
"metadata": {
"mypartner_settlementId": "my_settlement_id",
"externalReference": "REF123456"
}
}
```
When the client accepts their funds, the corresponding transaction object will have this same metadata.
## URL Generation
The `url` field is only populated when the disbursement reaches `APPROVED` status. This means:
* Disbursements created in `DRAFT` status will have `url: null`
* Disbursements created in `APPROVED` status will have a `url` immediately
* Disbursements approved after creation (using `disbursementApprove`) will receive a `url` at that time
* The `url` will change whenever the disbursement is updated, so it's important to provide clients with the updated URL after any changes
## Example Request
```json theme={"system"}
{
"input": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"amount": 25000,
"authorizedIdentities": [
{
"type": "EMAIL",
"value": "john.doe@example.com"
},
{
"type": "PHONE",
"value": "+15551234567"
}
],
"fundingAccountId": "bank_account_123",
"clientUpsert": {
"id": "client-uuid-here",
"clientName": "John Doe",
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe"
},
"matterUpsert": {
"id": "matter-uuid-here",
"clientId": "client-uuid-here",
"name": "Settlement Case 2024"
},
"allowedMethods": ["ACH", "PUSH_TO_CARD"],
"enhancedSecurity": true,
"expiryDays": 30,
"metadata": {
"settlementId": "SETT-2024-001"
},
"status": "APPROVED"
}
}
```
## Example Response
```json theme={"system"}
{
"data": {
"disbursementCreate": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"amount": 25000,
"status": "APPROVED",
"url": "https://app.confidolegal.com/d/abc123xyz",
"authorizedIdentities": [
{
"type": "EMAIL",
"value": "john.doe@example.com"
},
{
"type": "PHONE",
"value": "+15551234567"
}
],
"client": {
"id": "client-uuid-here",
"clientName": "John Doe"
},
"matter": {
"id": "matter-uuid-here",
"name": "Settlement Case 2024"
},
"fundingAccount": {
"id": "bank_account_123"
}
}
}
}
```
## Authorization
* **FIRM\_ADMIN** users can create disbursements in either `DRAFT` or `APPROVED` status
* **FIRM\_USER** users can only create disbursements in `DRAFT` status and will receive an error if they attempt to create an `APPROVED` disbursement
## Related Operations
* To approve a draft disbursement, use the [`disbursementApprove`](/docs/send-money/disbursements) mutation
* To update a disbursement, use the [`disbursementUpdate`](/docs/send-money/disbursements) mutation
* To create multiple disbursements at once, see [Bulk Create Disbursements](/docs/send-money/bulk-disbursements)
# Disbursements
Source: https://docs.confidolegal.com/docs/send-money/disbursements
Securely send money to Clients with Disbursements. Here's the high-level overview.
To save a bank account or debit card in **your application** and pay it later, see the [Stored Disbursement Methods](/docs/embedded-forms/stored-disbursement-methods) guide.
### Prerequisites
Firms must be **`ACTIVE`** for payments in general, but **disbursements are gated separately**: the API returns an error if disbursements are not enabled for that Firm (`disbursements are not allowed for this firm`). Enabling disbursements is an additional setup step in onboarding, not something that automatically turns on the moment `firm.status` becomes `ACTIVE`. If creation fails, the Firm needs disbursements enabled in Confido (completed by Confido operations)—contact if you are unsure of the Firm’s status.
Create a disbursement link and specify the email or phone number that can
access the link.
Get the link to the Client. The Client can then access the link to accept
the disbursement.
Client unlocks link by verifying ownership of email or phone. Client enters
account information.
Funds are sent from the Firm's account to the Client's account.
## The Disbursement Object
For complete details of all available fields, see the Disbursement Object in our [GraphQL Playground](https://api.sandbox.gravity-legal.com/).
```typescript theme={"system"}
type Disbursement = {
allowedMethods: DisbursementMethod[];
amount: Int;
authorizedIdentities: Identity[];
client?: Client;
fundingAccount: BankAccount;
id: string;
matter?: Matter;
metadata?: JSON;
status: DisbursementStatus;
url?: string;
};
```
**allowedMethods: DisbursementMethod\[]**
A list of methods that the client is allowed to use when accepting the disbursement from the payout page. The allowed methods will fall back to the Firm’s defaults unless set specifically on the disbursement.
`DisbursementMethod.ACH` Slow ACH transaction via Plaid or manually entering account number and routing number.
`DisbursementMethod.PUSH_TO_CARD` Instant transaction to a Visa or Mastercard debit card.
`DisbursementMethod.REQUEST_CHECK` Firm would be informed that the client wants a check sent to them.
**amount: Int**
The amount to disburse to the client. If the client chooses an instant method from the payout page, the net disbursement to them will be **amount - instant\_fee**.
**authorizedIdentities: Identity\[]**
The identities that are authorized to accept the disbursement. Any of the authorized identities can be used on the payout page to access the disbursement. After the client authenticates on the payout page, we verify that the email or phone they used matches one of the authorizedIdentities.
**client**
The client that the disbursement is associated with. This is optional.
**fundingAccount**
The bank account the disbursement will come out of.
**id**
Unique identifier for the disbursement. UUID format.
**matter**
The matter the disbursement is associated with. This is optional.
**metadata**
A flexible JSON object that allows you to store and transfer arbitrary data. When a transaction is created for the disbursement, this metadata object is copied and added to the transaction. This lets you associate transactions with external entities and pass through custom data as needed.
**status**
`DRAFT` The disbursement has been created but has not been approved. At this stage there is no `url` and no funds can be transferred.
`APPROVED` The disbursement has been approved and is ready for the client to accept. At this status, the disbursement will have a unique `url`.
`FUNDS_IN_TRANSIT` The client has visited the payout page, entered their method details, and accepted the funds but the funds have not reached the client’s account yet.
`DELIVERED` The client has received the funds in their account.
**transactions: Transaction\[]**
When the client accepts the disbursement a Transaction is created. Most disbursements will only have one transaction in this list.
A disbursement may have two transactions in scenarios such as ACH returns. For example, if an ACH payout is initiated but the receiving bank rejects it, the transaction is returned. In this case, the disbursement would include two transactions: one for the initial payout and one for the ACH return.
**url**
The URL where the payout page is hosted, allowing the client to accept their funds. This field is nullable and will only be populated once the disbursement reaches the APPROVED status. The URL will change whenever the disbursement is updated, so it's important to provide the client with the new URL after any update, as the previous one will no longer be valid.
## Create a Disbursement
Use the `disbursementCreate` mutation to create a new disbursement.
**Input**
```typescript theme={"system"}
type DisbursementCreateInput = {
allowedMethods?: DisbursementMethod[];
amount: number; // in cents
authorizedIdentities: AuthorizedIdentity[];
clientId?: string;
clientUpsert?: ClientCreateInput;
fundingAccountId: string;
matterId?: string;
matterUpsert?: MatterCreateInput;
metadata?: JSON;
status?: DRAFT | APPROVED;
};
```
**allowedMethods**
This lets you specify certain allowed methods for this disbursement only. If left blank, the disbursement will fall back to the Firm’s default methods.
**clientUpsert**
Links an existing client or creates and links a new one to the disbursement. This is useful for checking if the client is already in the system and linking them, or creating a new record if needed. This should not be used with `clientId`.
**metadata**
A flexible JSON object that allows you to store and transfer arbitrary data. When a transaction is created for the disbursement, this metadata object is copied and saved on the transaction. This lets you associate transactions with external entities and pass through custom data as needed. For example, you could create a disbursement with `metadata = { mypartner_settlementId: 'my_settlement_id' }`. Then, when the client accepts their funds, the corresponding transaction object would have this same metadata.
**status**
You can pass `status=APPROVED` into the create call if you want to skip the approval process. Otherwise, the disbursement will be created in `DRAFT` and will need to be approved.
## Update a Disbursement
Use the `disbursementUpdate` mutation to update a disbursement. Disbursements can only be updated in `DRAFT` and `APPROVED` status.
**Input**
```
type DisbursementUpdateInput = {
allowedMethods?: DisbursementMethod[];
amount: number; // in cents
authorizedIdentities: AuthorizedIdentity[];
clientId?: string;
clientUpsert?: CreateClientInput;
fundingAccountId: string;
matterId?: string;
matterUpsert?: CreateMatterInput;
}
```
## Approve a Disbursement
Use the `disbursementApprove` mutation to approve a disbursement. If successful, the disbursement will now have a unique URL.
## Unapprove a Disbursement
You can use the `disbursementUnapprove` mutation to send the disbursement back to `DRAFT` and invalidate the previous URL. When the disbursement is approved again, you will get a new URL.
## Delete a Disbursement
Use the `disbursementDelete` mutation to delete a Disbursement. Disbursements can only be deleted in `DRAFT` and `APPROVED` status.
## Get a Disbursement
Fetch a single disbursement with `disbursementGet` query.
## List Disbursements
List disbursements that match the given query params with `disbursementList`.
For a full list of available query parameters, see our [GraphQL Playground](https://api.sandbox.gravity-legal.com/).
## Webhooks
Get a webhook when a Disbursement is updated with the `disbursement.updated` webhook. Also get webhooks when a Disbursement is accepted via the `transaction.created` webhook. See the [webhooks docs](https://docs.confidolegal.com/docs/webhooks/configure)
# Expiration
Source: https://docs.confidolegal.com/docs/send-money/expiration
For additional security, all disbursement links will expire after a configurable number of days from approval. The `expiryDays` can be configured at the firm level or at the disbursement level.
By default, all disbursements will expire 60 days after they have been approved. When a disbursement expires, the `disbursement.updated` webhook is fired.
# Overview
Source: https://docs.confidolegal.com/docs/statements/overview
Statements are generated every month to outline metrics for the firm and inform the firm on the debits that will be made against their bank accounts for fees. Based on your desired functionality, we have two methods for giving your firms their statement data.
### Creating your own statement
All statement data is accessible through our GraphQL API. The Statement object has everything you would need to build a customized statement for the firm branded to your own application.
The `bankAccounts` field on the statement object outlines the fees associated with each of the firm's individual bank accounts. If a firm has surcharging enabled, the `surchargeFeesCollected` can be subtracted from the `cardFees` field to determine the firm's actual `netFees`. The fees are broken up in this manner because surcharging fees are deposited into the firm's bank account at the time of the payment and then taken out at the end of the month.
The `debits` field on the statement object outlines the debits that will be taken out of the firm's fee bank account.
The `additionalCredits` and `additionalDebits` fields are catch-all locations for any additional changes that occurred (fee waivers, chargeback reversals, etc.).
### Utilizing the Confido Legal PDF
You can also query the GraphQL API for the `statement.pdfUrl` field. This is a short-term URL that can be used to either download the PDF or display the statement in a UI.
Warning: The `pdfUrl` should not be sent out through an email because it is a
short-lived link. If you would like to send the PDF through an email, the PDF
should be downloaded with the `pdfUrl` and added as an attachment to the
email.
### Webhooks
View the `statement.created` and `statement.updated` webhooks [here](/docs/webhooks/types).
### Sandbox environment
We have exposed two GraphQL Mutations to help with testing the statements process.
* `sandboxOnlyCreateMockStatement` This mutation can be used to generate an example statement for a specific month. The values in the statement will be completely random and will not relate to the firm's actual sandbox payments. This will also trigger the `statement.created` webhook so you can test that flow as well.
* `sandboxOnlyUpdateMockStatement` This mutation will update a specific statement with new random values. This will also trigger the `statement.updated` webhook so you test that flow as well.
# Configure
Source: https://docs.confidolegal.com/docs/webhooks/configure
Webhooks notify you when things happen in Confido so your integration can react to them.
By registering webhook endpoints, you enable Confido to send webhook payloads as part of POST requests to these endpoints.
The body of these POST requests will contain a list of webhook payloads. The payloads contain minimal data and are meant to be followed by a query to our API to grab the specific data you need.
## Webhook Payloads
Here is an example body with a single `transaction.created` payload. Notice how only the `transaction.id` was delivered. When processing this webhook, you'll use this `id` to query our API and get the latest state of the transaction.
```javascript theme={"system"}
[
{
data: {
transaction: {
id: '9c2dbhy6-08d9-4d94-971b-8b594c2ea13c',
},
},
type: 'transaction.created',
firmId: 'fcab45p5-c4cb-427d-a144-b3f11f227658',
eventId: '2e9e4123-9568-4f34-9ae2-836f760f03b8',
},
];
```
### Payload Fields
The data field contains the specific data for the webhook type. It is a JSON
object that will vary based on the webhook event type.
The type of webhook event. This is a string value that defines the specific
event that occurred, such as `transaction.created`, `firm.updated`, etc.
The id of the firm this webhook event is associated with.
The unique id for this event. This can be used as an idempotent key to ensure
you do not take multiple actions for the same webhook event.
## Configure Webhooks
You can register endpoints and configure which webhook types you are interested in through the [Partner Portal](https://app.sandbox.confidolegal.com/) under `Settings > Webhooks`.
You will not receive webhooks for types that are not selected.
## HMAC Signature
Webhook requests will include an HMAC Signature under the `X-SIGNATURE` header. This can be used along with the webhook secret found in the Partner Portal to verify the webhook came from Confido and has not been tampered.
To verify the HMAC Signature, create a `sha512` HMAC with your webhook secret, update it with the webhook payload, and ensure it is equal to the `X-SIGNATURE` header received with the webhook.
Here is some example code written in TypeScript.
```javascript theme={"system"}
import crypto from "crypto";
export function verifyHmacSignature(
webhookPayload: WebhookPayload,
webhookSecret: string,
xSignatureHeader: string
): boolean {
const hmac = crypto.createHmac("sha512", webhookSecret);
hmac.update(JSON.stringify(webhookPayload));
const hmacSignature = hmac.digest("base64");
return hmacSignature === xSignatureHeader;
}
```
## Retry Policy
If we do not receive a 200 level response while posting to your endpoint after 5 seconds, the webhook event enters a `failed` state. All webhook events in the `failed` state are retried with the following backoff strategy: 5 min, 20 min, 60 min, 1 day, dead letter.
## Event history and resend
Partners can inspect recent delivery attempts for a registered webhook URL and manually trigger a resend:
* In the **Partner Portal**, open your webhook configuration and use the event list for that URL (including failed and dead-lettered events) to **resend** a payload.
* Via the GraphQL API (authenticated as a Partner Admin), use the `webhookEventsList` query (scoped to a `webhookUrlId`) to list events, and the `webhookEventResend` mutation to retry delivery for a specific `eventId`.
Use `eventId` from the webhook body as your idempotency key when processing. Resends reuse that same `eventId`, so duplicate delivery should be safe if your handler is idempotent.
There is no separate “replay all since timestamp”; for large gaps, combine polling the API with portal resends, or contact support if you need operational help after extended outages.
## Disabling Webhook URLs
If a webhook event dead letters, we check if the webhook URL has received any successful responses in the last 24 hours. If it has not, we disable the webhook URL and it must be re-enabled through our Partner portal.
# Webhook Types
Source: https://docs.confidolegal.com/docs/webhooks/webhook-types
Make sure you configure Confido to send the webhook types you are interested in through the Partner Portal under `Settings > Webhooks`.
## disbursement.updated
Sent when a disbursement is updated.
```json theme={"system"}
{
"data": {
"disbursement": {
"id": "string",
"allowedMethods": "DisbursementMethod[]",
"amount": "int",
"authorizedIdentities": "array[]",
"clientId": "string",
"fundingAccountId": "string",
"matterId": "string",
"metadata": "object",
"publicKey": "string",
"status": "DisbursementStatus"
},
"oldDisbursement": {
"id": "string",
"allowedMethods": "DisbursementMethod[]",
"amount": "int",
"authorizedIdentities": "array[]",
"clientId": "string",
"fundingAccountId": "string",
"matterId": "string",
"metadata": "object",
"publicKey": "string",
"status": "DisbursementStatus"
}
},
"type": "disbursement.updated",
"firmId": "",
"eventId": ""
}
```
## firm.updated
Sent when a Firm is updated. This includes when the Firm's status changes.
```json theme={"system"}
{
"data": {
"firm": {
"id": "string"
}
},
"type": "firm.updated",
"firmId": "",
"eventId": ""
}
```
## statement.created
Emitted when a monthly statement has been created for a Firm.
```json theme={"system"}
{
"data": {
"statement": {
"id": "string"
}
},
"type": "statement.created",
"firmId": "",
"eventId": ""
}
```
## statement.updated
Emitted if a statement is updated. It is very unlikely this will be emitted. All statements should be finalized when the `statement.created` webhook is emitted.
```json theme={"system"}
{
"data": {
"statement": {
"id": "string"
}
},
"type": "statement.updated",
"firmId": "",
"eventId": ""
}
```
## stored\_payment\_method.created
Emitted when a new Stored Payment Method is created.
```json theme={"system"}
{
"data": {
"storedPaymentMethod": {
"id": "string"
}
},
"type": "stored_payment_method.created",
"firmId": "",
"eventId": ""
}
```
## stored\_payment\_method.deleted
Emitted when a Stored Payment Method is deleted.
```json theme={"system"}
{
"data": {
"storedPaymentMethod": {
"id": "string"
}
},
"type": "stored_payment_method.deleted",
"firmId": "",
"eventId": ""
}
```
## stored\_payment\_method.updated
Emitted when a Stored Payment Method is updated.
```json theme={"system"}
{
"data": {
"storedPaymentMethod": {
"id": "string"
}
},
"type": "stored_payment_method.updated",
"firmId": "",
"eventId": ""
}
```
## stored\_disbursement\_method.created
Emitted when a new Stored Disbursement Method is created (including via the [iframe embed](/docs/embedded-forms/stored-disbursement-methods)).
```json theme={"system"}
{
"data": {
"storedDisbursementMethod": {
"id": "string"
}
},
"type": "stored_disbursement_method.created",
"firmId": "",
"eventId": ""
}
```
## stored\_disbursement\_method.deleted
Emitted when a Stored Disbursement Method is deleted.
```json theme={"system"}
{
"data": {
"storedDisbursementMethod": {
"id": "string"
}
},
"type": "stored_disbursement_method.deleted",
"firmId": "",
"eventId": ""
}
```
## stored\_disbursement\_method.updated
Emitted when a Stored Disbursement Method is updated.
```json theme={"system"}
{
"data": {
"storedDisbursementMethod": {
"id": "string"
}
},
"type": "stored_disbursement_method.updated",
"firmId": "",
"eventId": ""
}
```
## transaction.created
Sent when a new Transaction is created. You can find the full data typing in our [API Sandbox](https://api.sandbox.gravity-legal.com/) under root > Query > transaction.
```ts theme={"system"}
{transaction: {
id: string;
achReturnCode: string | null;
achReturnReason: string | null;
aggregatePaymentLink: { id: string } | null;
amountProcessed: number;
amountRefunded: number;
bankAccount: { id: string };
cardBrand: string | null;
client: { id: string } | null;
createdOn: Date;
disbursement: { id: string } | null;
disbursementAmounts: DisbursementAmounts | null;
errorMessage: string | null;
firm: { id: string };
lastFour: string | null;
matter: { id: string } | null;
payerEmail: string | null;
payerName: string | null;
payerNotes: string | null;
paymentMethod: TransactionPaymentMethod;
payment: { id: string } | null;
paymentLink: { id: string } | null;
settledOn: Date | null;
standingLink: { id: string } | null;
status_v2: PaymentTxnStatus2;
storedPaymentMethod: { id: string } | null;
subscription: { id: string } | null;
type: PaymentTxnType;
}
};
```
## transaction.funds\_in\_transit
Sent when a Transaction moves to FUNDS\_IN\_TRANSIT.
```json theme={"system"}
{
"data": {
"transaction": {
"id": "string"
}
},
"type": "transaction.funds_in_transit",
"firmId": "",
"eventId": ""
}
```
## transaction.deposited
Sent when a Transaction is deposited in the Firm's account.
```json theme={"system"}
{
"data": {
"transaction": {
"id": "string"
}
},
"type": "transaction.deposited",
"firmId": "",
"eventId": ""
}
```
## transaction.voided
Sent when a Transaction is voided.
```json theme={"system"}
{
"data": {
"originalTransaction": {
"id": "string"
},
"voidTransaction": {
"id": "string"
}
},
"type": "transaction.voided",
"firmId": "",
"eventId": ""
}
```
## transaction.refunded
Sent when a Transaction is fully refunded.
```json theme={"system"}
{
"data": {
"originalTransaction": {
"id": "string"
},
"refundTransaction": {
"id": "string"
}
},
"type": "transaction.refunded",
"firmId": "",
"eventId": ""
}
```
## transaction.partially\_refunded
Sent when a Transaction is partially refunded.
```json theme={"system"}
{
"data": {
"originalTransaction": {
"id": "string"
},
"refundTransaction": {
"id": "string"
}
},
"type": "transaction.partially_refunded",
"firmId": "",
"eventId": ""
}
```
## transaction.ach\_returned
Sent when an ACH Transaction is returned.
```json theme={"system"}
{
"data": {
"originalTransaction": {
"id": "string"
},
"returnTransaction": {
"id": "string"
}
},
"type": "transaction.ach_returned",
"firmId": "",
"eventId": ""
}
```
# CodeSandbox
Source: https://docs.confidolegal.com/examples/code-sandbox
Our Code Sandboxes are short, targeted examples showcasing one piece of the platform. They are meant to be easy to follow references, but by no means, production-ready code.
We've hosted the Legal Wave application here. Sign up for an account and
connect a Confido Legal sandbox account to try it out.
### Hosted Fields - Card and ACH Form
This example shows how to use a Payment Session to collect a card and ACH payment using Hosted Fields.
Hosted Fields - Card and ACH Form
# Legal Wave
Source: https://docs.confidolegal.com/examples/legal-wave
[Legal Wave](https://confido-legal-wave.vercel.app) is an example application that highlights what can be done with the Confido Legal platform.
The code is open source and available on [GitHub](https://github.com/confido-legal/confido-legal-wave). It utilizes the popular [Next.js](https://nextjs.org) framework and is written in TypeScript and React. We also use [Chakra UI](https://chakra-ui.com/) for UI components.
Legal Wave's user account structure mimics a SaaS practice management app which would have multiple Firms, each with their own data, using the application. It allows users to create an account, connect to their Confido Legal account, and accept payments through different payment vehicles. We will be continually updating this codebase.
We've hosted the Legal Wave application here. Sign up for an account and
connect a Confido Legal sandbox account to try it out.
Review the code on GitHub, clone the repo and run it locally.
# Change Events
Source: https://docs.confidolegal.com/hosted-fields-js/change-events
To provide a great UI/UX experience on your page you will want to know when changes happen within the fields. You can add any number of change listeners to the SDK that will be called when the internal state of the fields change.
```javascript theme={"system"}
const myListener = (e: ChangeEvent) => {
console.log(e);
};
window.confidoHostedFields.addChangeListener(myListener);
// then later, clean it up
window.confidoHostedFields.removeChangeListener(myListener);
```
## Event Types
| Type | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `bin_change` | Emitted when the first 9 digits of the card number field changes |
| `bin_loaded` | After the bin number changes, the SDK makes a network call to get more information about the current card, including the card type (debit or credit). This event is emitted after this network call returns. |
| `field_error_cleared` | Emitted when a field, that was previously in error, is fixed. |
| `fields_loaded` | Emitted when all the fields of the active form type are finished loading. |
| `initializing` | Emitted immediately following a call to `sdk.init()` |
| `initialized` | Emitted once initialization is done and successful. |
| `init_error` | Emitted when there's an error with initialization. |
## ChangeEvent Fields
The type of event. This can be one of the types listed above.
The current state of the fields. See the [State
Object](/hosted-fields-js/state-object) for more details.
# Change the Form Type
Source: https://docs.confidolegal.com/hosted-fields-js/change-form-type
As your UI switches between payment form types (`card`, `ach`), you must call this method to load the required fields.
Calling `setActiveForm()` changes the fields that are loaded. You must ensure that the elements where the fields will be loaded are in the DOM before calling this method.
```javascript theme={"system"}
window.confidoHostedFields.setActiveForm(type: FormType): void;
```
# Initialize
Source: https://docs.confidolegal.com/hosted-fields-js/initialize
You can initialize the fields by calling the `init()` method. Once this method is called, the SDK will emit the `initializing`, `initialized`, and/or the `init_error` [change events](/hosted-fields-js/change-events).
```javascript theme={"system"}
window.confidoHostedFields.init(options: Options): Promise;
```
You can call `init()` multiple times in the same context. Each call will trigger a new `initializing`, `initialized`, and/or `init_error` sequence. The fields will also be reloaded each time `init()` is called.
## Example
Example initializing the hosted fields for a Payment session.
```typescript theme={"system"}
async function initHostedFields() {
const res = await getPaymentTokenFromMyBackend();
const fieldStyles = {
border: 'none',
color: 'rgb(26, 32, 44)',
'font-family':
'-apple-system, "system-ui", "Segoe UI", Helvetica, Arial, sans-serif',
'font-weight': 400,
height: '38px',
width: '100%',
'font-size': '16px',
};
window.confidoHostedFields.init({
paymentToken: res.paymentToken,
activeForm: 'card',
fields: {
accountNumber: {
containerId: 'account-number',
style: fieldStyles,
},
accountHolderName: {
containerId: 'account-holder-name',
style: fieldStyles,
},
routingNumber: {
containerId: 'routing-number',
style: fieldStyles,
},
cardNumber: {
containerId: 'card-number',
style: fieldStyles,
},
cardExpirationDate: {
containerId: 'card-exp',
style: fieldStyles,
},
cardSecurityCode: {
containerId: 'card-cvv',
style: fieldStyles,
},
},
});
}
```
## Method Params Object
Payment Token returned from a `paymentSessionCreate` mutation. Required when
using a Payment Session type.
Token returned from a `createSavePaymentMethodToken` mutation call. Required
when using a Save Payment Method Session type.
The initial active form. This can be either `card` or `ach`. The active form
determines which fields are loaded.
Configuration for the hosted fields.
Configuration for the card number field. Required when activeForm is
`card`
Configuration for the card expiration date field. Required when activeForm
is `card`.
Configuration for the card security code field. Required when activeForm
is `card`
Field config for the account holder name field for ACH payments. Required
when supporting ACH payments.
Field config for the routing number field for ACH payments. Required when
supporting ACH payments.
Field config for the account number field for ACH payments. Required when
supporting ACH payments.
# Overview
Source: https://docs.confidolegal.com/hosted-fields-js/overview
This is a reference guide for the Hosted Fields SDK. The SDK is a JavaScript file which should be loaded via a `script` tag on any page where you want to load our hosted fields. Once the script loads the `window` object will have a `window.confidoHostedFields` object with the methods documented here. The `confidoHostedFields` object is a singleton. This means you can only have one instance of hosted fields per `window`.
If you are looking to get started quickly, see the Hosted Fields [Quick Start Guide](/docs/hosted-fields/overview).
You can use Hosted Fields when accepting a payment (Payment Session) or saving a payment method for later use (Save Payment Method Session). The main difference between the two are the API calls you will make before you initialize and after the fields have been submitted.
## Including the script
Include the script on any page you need to load the Hosted Fields.
```html theme={"system"}
```
The script will use `window.postMessage` to post a message when it has finished loading and is ready for initialization. You can set up a listener to get notified.
```typescript theme={"system"}
const myListener = (e) => {
if (e.data === 'confido_hosted_fields_script_loaded') {
console.log('sdk is loaded!');
}
};
window.addEventListener('message', myListener);
```
# The State Object
Source: https://docs.confidolegal.com/hosted-fields-js/state-object
The state object represents the internal state of the fields. The current state is included on every [change event](/hosted-fields-js/change-events).
You can also always call the `getState()` method to retrieve the current state of the fields.
```javascript theme={"system"}
const currentState = window.confidoHostedFields.getState();
```
Example
```typescript theme={"system"}
{
activeForm: 'card',
cardData: {
bin: '424242',
cardType: 'credit',
brand: 'visa',
},
env: 'sandbox',
fields: {
cardNumber: {
containerId: 'card-number',
loading: false,
error: null,
},
cardExpirationDate: {
containerId: 'card-expiration-date',
loading: false,
error: null,
},
cardSecurityCode: {
containerId: 'card-security-code',
loading: false,
error: null,
},
accountNumber: {
containerId: 'account-number',
loading: false,
error: null,
},
routingNumber: {
containerId: 'routing-number',
loading: false,
error: null,
},
accountHolderName: {
containerId: 'account-holder-name',
loading: false,
error: null,
},
},
initializing: false,
isSubmitting: false,
paymentLink?: PaymentLink;
paymentMethod?: PaymentMethod;
sessionType: SessionType;
surcharging: {
active: true,
willBeApplied: true,
}
```
## Props
The current active form. This can be either `card` or `ach`. The active form
determines which fields are loaded.
Information about the card number that was entered. This is only populated
when the `activeForm` is `card` and the card number field has been filled.
The first six digits of the card number that was entered.
The type of card that was entered. This can be either `credit` or `debit`.
The card brand that was entered, such as `visa`, `mastercard`, etc.
The processing environment. This can be either `sandbox` or `production`.
The state of the hosted fields.
The state of the card number field.
The state of the card expiration date field.
The state of the card security code field.
The state of the account number field.
The state of the routing number field.
The state of the account holder name field.
`true` when the SDK is still initializing.
`true` after `sdk.submitFields()` is called and before the fields are done
submitting their data.
The payment link object, if applicable.
The unique identifier for the payment link.
The amounts associated with the payment link.
The amount in cents.
The category of the bank account, e.g., `trust`, `operating`.
The maximum ACH payment amount in cents. You should use this to show a
warning if your amount is over this limit.
This indicates the payment method for the transaction. When `activeForm` is
`ach`, this will be `ACH`. When `activeForm` is `card`, this will be either
`CREDIT` or `DEBIT` depending on the card type. Possible values are: `ACH` |
`CREDIT` | `DEBIT`
The type of session. Possible values are: `payment` | `save_payment_method`
Surcharging information. This is only applicable when the `activeForm` is
`card`. It provides details about whether surcharging is active, the rate, and
whether it will be applied.
Whether surcharging is active.
Whether the surcharge rate will be applied or not. If surcharging is
enabled by the firm, this will show as `true` when the `cardNumber` field
has been filled out and the `bin_loaded` event has been produced by the
form.
The surcharge rate that will be multiplied by the amount and added to the
payment.
Set when there is an initialization error.
The error message describing the initialization error.
# Submitting the Fields
Source: https://docs.confidolegal.com/hosted-fields-js/submitting-fields
Call this method when you are ready to submit the data in the fields. This method returns a Promise that you can `await` or you can pass in a `callback`. The `callback` will be invoked when the field data has been submitted successfully or there was an error.
Once the data is submitted successfully, you are ready to finish the payment on your server.
```javascript theme={"system"}
window.confidoHostedFields.submitFields(callback: SubmitFieldsCallback);
```
### Example with Promise
```javascript theme={"system"}
const handleSubmit = async () => {
try {
await window.confidoHostedFields.submitFields();
// the fields were submitted successfully
} catch (error) {
// do something to show the error
}
};
```
### Example with callback
```javascript theme={"system"}
const handleSubmit = () => {
window.confidoHostedFields.submitFields((error) => {
if (error) {
// do something to show the error
return;
}
// else the fields were submitted successfully
});
};
```
# Voids and Refunds Migration
Source: https://docs.confidolegal.com/migrations/voids-and-refunds-migration
Due to a change in our sponsor bank to improve our onboarding process, it is now possible for refunds and voids to take more than 60 seconds to complete. Because of this, we have updated our void and refund mutations to accommodate the asynchronous process.
## What's Changed
The following three new mutations have been added to replace the legacy synchronous mutations:
* `transactionRefund` (replaces `refundTransaction`)
* `transactionVoid` (replaces `voidTransaction`)
* `transactionVoidOrRefund` (replaces `voidOrRefundTransaction`)
**Key Changes:**
* Operations are now asynchronous and may take longer to complete
* New response structure separates request status from transaction results
* Transaction lists in responses are nullable and may be empty initially
* Status checking is required to determine operation completion
## Migration Timeline
All refunds and voids for firms onboarded using the new sponsor bank will
return a *Not implemented* error.
## Implementation Guide
### Refunds
##### Legacy Mutation (Deprecated)
```graphql theme={"system"}
mutation LegacyRefundTransaction($input: RefundTransactionInput!) {
refundTransaction(input: $input) {
id
amountProcessed
amountRefunded
errorMessage
surchargeAmount
surchargeAmountRefunded
}
}
```
##### New Mutation
```graphql theme={"system"}
mutation TransactionRefund($input: TransactionRefundInput!) {
transactionRefund(input: $input) {
refundRequest {
id
status
errorMessage
}
refundTransactions {
id
amountProcessed
amountRefunded
errorMessage
surchargeAmount
surchargeAmountRefunded
}
}
}
```
**⚠️ Important:** The `refundTransactions` list is **nullable** and may be empty initially for asynchronous operations.
**Recommended Implementation Pattern:**
1. Check `refundRequest.status` first:
* `SUCCESS`: Refund completed, check `refundTransactions` for results
* `AWAITING_RESULT`: Refund is processing, listen for a `transaction.refunded` or `transaction.partially_refunded` webhook
* `TXN_FAILED` or `ERROR`: Check `refundRequest.errorMessage` for details
2. If status is `AWAITING_RESULT`, you'll need to listen to webhooks to get the final result
### Voids
##### Legacy Mutation (Deprecated)
```graphql theme={"system"}
mutation LegacyVoidTransaction($input: VoidTransactionInput!) {
voidTransaction(input: $input) {
id
amountProcessed
amountRefunded
errorMessage
surchargeAmount
surchargeAmountRefunded
}
}
```
##### New Mutation
```graphql theme={"system"}
mutation TransactionVoid($input: TransactionVoidInput!) {
transactionVoid(input: $input) {
voidRequest {
id
status
errorMessage
}
voidTransactions {
id
amountProcessed
amountRefunded
errorMessage
surchargeAmount
surchargeAmountRefunded
}
}
}
```
**⚠️ Important:** The `voidTransactions` list is **nullable** and may be empty initially for asynchronous operations.
**Recommended Implementation Pattern:**
1. Check `voidRequest.status` first:
* `SUCCESS`: Void completed, check `voidTransactions` for results
* `AWAITING_RESULT`: Void is processing, listen for a `transaction.voided` webhook
* `TXN_FAILED` or `ERROR`: Check `voidRequest.errorMessage` for details
2. If status is `AWAITING_RESULT`, you'll need to listen to webhooks to get the final result
### Void or Refund
##### Legacy Mutation (Deprecated)
```graphql theme={"system"}
mutation LegacyVoidOrRefundTransaction($input: VoidOrRefundTransactionInput!) {
voidOrRefundTransaction(input: $input) {
id
amountProcessed
amountRefunded
errorMessage
surchargeAmount
surchargeAmountRefunded
}
}
```
##### New Mutation
```graphql theme={"system"}
mutation TransactionVoidOrRefund($input: TransactionVoidOrRefundInput!) {
transactionVoidOrRefund(input: $input) {
status
type
voidOrRefundTransactions {
id
amountProcessed
amountRefunded
errorMessage
surchargeAmount
surchargeAmountRefunded
}
}
}
```
**⚠️ Important:** The `voidOrRefundTransactions` list is **nullable** and may be empty initially for asynchronous operations.
**Recommended Implementation Pattern:**
1. Check `voidOrRefundRequest.status` first:
* `SUCCESS`: Operation completed, check `voidOrRefundTransactions` for results
* `AWAITING_RESULT`: Operation is processing, listen for a `transaction.voided` or `transaction.refunded` webhook
* `TXN_FAILED` or `ERROR`: Check `voidOrRefundRequest.errorMessage` for details
2. Check `voidOrRefundRequest.type` to determine if a `VOID` or `REFUND` was performed
3. If status is `AWAITING_RESULT`, you'll need to listen to webhooks to get the final result
## Handling Asynchronous Operations
### Webhooks
Implement webhook handlers to receive notifications when operations complete. This provides real-time updates when your refund, void, or void-or-refund operations finish processing.
Relevant webhook events:
* `transaction.refunded` - Triggered when a refund completes
* `transaction.partially_refunded` - Triggered when a partial refund completes
* `transaction.voided` - Triggered when a void completes
## Troubleshooting
* **Null transaction lists**: This is expected behavior for asynchronous operations. Always check the request status first.
* **AWAITING\_RESULT status**: The operation is still processing. Use webhooks to get updates.
* **Error handling**: Always check for `errorMessage` fields in both request and transaction objects.
## Support
If you encounter issues during migration, please contact our engineering team through Slack.
# Change Events
Source: https://docs.confidolegal.com/onboarding-js/change-events
Onboarding.js will call the `onChange` handler passed into `renderForm` when certain events happen within the form.
## Event Types
| Type | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `error_on_file_upload` | There was an error when trying to upload a file. |
| `error_on_load` | There was an error loading the form. |
| `error_on_save` | There was an error while saving the form data. |
| `error_on_submit` | There was an error trying to submit the form. |
| `loaded` | The form loaded successfully and is ready for input. |
| `saved` | All progress has been saved. It would be safe to navigate away from the form. |
| `submitted` | All data has been saved and validated. The Firm will now be sent to underwriting. |
| `token_expired` | The onboarding token has expired. You must create a new one and re-render. |
| `token_expires_soon` | The onboarding token will expire in five or fewer minutes. The user will want to save their progress so they don't lose unsaved data. |
## ChangeEvent
The type of event. This can be one of the types listed above.
The error message. Only set if this event is an error type.
Object containing all the currently saved data in the form. This will not
include data that has been entered but not saved.
An ISO timestamp of the time at which the token will expire.
# Create an Onboarding Token
Source: https://docs.confidolegal.com/onboarding-js/create-onboarding-token
To render the form, you will need a public Onboarding Token. The token is used by onboarding.js for authorization and scopes the requests to the Confido Firm that it was created for.
Onboarding Tokens can be shared with the client and are valid for 24 hours. If a token expires, you will need to create a new one and re-render the form.
There are two ways to create an Onboarding Token:
1. Directly through the `createOnboardingToken` mutation. Use this when you already have a Firm API Token.
2. By requesting a token in the result of the `createFirm` mutation. Use this when you know you want to render the form soon after you create the Firm.
## `createOnboardingToken` mutation
Use the `createOnboardingToken` mutation when the Firm is already created in Confido.
```graphql theme={"system"}
mutation CreateOnboardingToken {
createOnboardingToken {
token
}
}
```
This is a Firm-level request, so you must use a Firm API Token on the
`x-api-key` header. This tells us which Firm you want to create the token for.
## In `createFirm` Result
You can also get an Onboarding Token in the result of a `createFirm` mutation. This is useful if you would like to immediately render the onboarding form after creating a Firm. Remember, Onboarding Tokens are valid for 24 hours.
```graphql theme={"system"}
mutation CreateFirm() {
createFirm() {
onboardingToken {
token
}
}
}
```
You can find detailed information about all queries and mutations in our
[GraphQL Playground](https://api.sandbox.gravity-legal.com/v2).
Once you have a token, you will pass it to the browser where you can render the form.
# Custom Styles
Source: https://docs.confidolegal.com/onboarding-js/custom-styles
You can pass a `style` object to the `renderForm` which will apply custom styles to the onboarding form.
```javascript theme={"system"}
const style = {
theme: {
brandColors: {
50: '#F0FFF4',
100: '#C6F6D5',
200: '#9AE6B4',
300: '#68D391',
400: '#48BB78',
500: '#38A169',
600: '#2F855A',
700: '#276749',
800: '#22543D',
900: '#1C4532',
},
},
inputBackgroundColor: '#fff',
};
```
## `theme.brandColors`
The form uses the `brandColors` object to add a little bit of color. Items like buttons and checkbox background colors use the `brandColors`.
Here's a snapshot of what the form would look like if you used the style object above.
# Include the script
Source: https://docs.confidolegal.com/onboarding-js/include-script
You must include the script on any page where you want to inject the Onboarding form.
```html sandbox theme={"system"}
```
```html production theme={"system"}
```
Once the script loads, the `window` object will have a `window.confidoOnboarding` object with all the methods required to manage the onboarding process. The `confidoOnboarding` object has a method `renderForm` that we will call later to actually render the form. But first, we need to create an Onboarding Token.
# Overview
Source: https://docs.confidolegal.com/onboarding-js/overview
Onboarding.js is a JavaScript library that lets you inject the Confido Legal onboarding form into any `div` on your page. This means your users never have to leave your app to get approved to accept payments.
Here's how it works at a high level.
1. Include **onboarding.js** on your page through a `script` tag
2. Create an Onboarding Token for a Firm
3. Call **onboarding.js**'s `renderForm` method providing the Onboarding Token and a `containerId` to render the form into
4. The user fills out the form and **onboarding.js** communicates directly with Confido to submit data.
### Sequence Diagram
# Owner Invite Links
Source: https://docs.confidolegal.com/onboarding-js/owner-invite-links
To comply with standards we need to collect personal information for any business owner that owns more than 25% of the company.
Sometimes, the owners are not the ones filling out the onboarding form. In these cases, the user filling the form can generate a unique link and send it to the owner so they can fill in their own information.
This feature is enabled by default on the onboarding.js form. By default, the generated link will point to Confido Legal's owner information form. But you can create your own page using onboarding.js to keep users on your domain.
## Hosting an Owner Form on your domain
You can host your own owner form on your domain. This lets you keep users on your platform instead of directing them to Confido's owner form.
You can use onboarding.js to host an owner form. Similar to the `renderForm` function, there is also a `renderOwnerForm` function.
You pass a URL to the `renderForm` function under the `ownerInviteUrl` prop. This URL should be a page that you manage and that utilizes the `renderOwnerForm` function. When a user generates an owner invite link the returned URL will have a `o_code` query parameter. On your page, you will need to grab this param and pass it into `renderOwnerForm`.
For example, if you passed the following URL into `renderForm` under the `ownerInviteUrl` option:\
`https://my-app.com/owner-form`
Then the generated URL would look something like this:\
`https://my-app.com/owner-form?o_code=owner_d97b6badb88240f35c263357b21`
On your page, you would grab the `o_code` and pass it into the `renderOwnerForm` method of `confidoOnboarding`.
```js theme={"system"}
confidoOnboarding.renderOwnerForm({
code: 'owner_d97b6badb88240f35c263357b21',
containerId: 'my-container',
onChange?: myOnChangeHandler,
style?: ConfidoStyleOptions,
})
```
## Disabling Owner Invite Links
If you do not want to offer this function for your users, you can disable the feature by passing the `disableOwnerInvite: true` option to `renderForm`.
# Render the Form
Source: https://docs.confidolegal.com/onboarding-js/render-form
Now that you have an Onboarding Token and have passed it to the browser, you are ready to render the form.
```javascript theme={"system"}
window.confidoOnboarding.renderForm({
containerId: 'my-container-id',
token: onboardingToken,
disableOwnerInvite: true, // optional
onChange: myChangeHandler, // optional
style: {}, // optional
suppressLoadingSpinner: boolean, // optional, when true won't show a loading spinner
});
```
This function uses `document.getElementById` to find the `div` using the passed in `containerId` and injects the form there.
You can pass an optional `onChange` handler to `renderForm` which will get called when there are changes to the form.
## Example Blank Form

# Confido Core
Source: https://docs.confidolegal.com/salesforce/overview
## Setup & Configuration
### 1. Confido Settings (Custom Setting)
`Settings__c` — Hierarchy Custom Setting. Configure at **Setup → Custom Settings → Confido Settings → Manage**.
| Field | API Name | Description |
| :--------------------- | :------------------------- | :---------------------------------------------------------------------------------------- |
| Confido API Token | `ApiToken__c` | Your Confido API key. Required for all API calls. |
| Confido Webhook Secret | `WebhookSecret__c` | HMAC secret for verifying incoming webhooks. |
| Production Environment | `ProductionEnvironment__c` | Checkbox. Checked = `api.gravity-legal.com`; unchecked = `api.sandbox.gravity-legal.com`. |
### 2. Remote Site Settings
Pre-configured; both active by default:
* **production** → `https://api.gravity-legal.com`
* **sandbox** → `https://api.sandbox.gravity-legal.com`
### 3. Webhook Configuration
Point your Confido webhook to:
```
https:///services/apexrest/confido_core/webhook
```
The endpoint verifies the `X-SIGNATURE` header using HMAC-SHA512 with your `WebhookSecret__c`. Must be exposed via a Salesforce Site or Community (public REST resource).
### 4. Sync Bank Accounts
Bank accounts are managed in Confido, not Salesforce. Sync them before creating payment links using the LWC button or the invocable action.
***
### Client\_\_c
| Field | Description |
| :-------------------- | :------------------------------------------- |
| `Name` | Client name (synced to Confido) |
| `ConfidoId__c` | Text(36), unique external ID, auto-populated |
| `SyncErrorMessage__c` | Long Text, populated if last sync failed |
### Matter\_\_c
| Field | Description |
| :-------------------- | :------------------- |
| `Name` | Matter name |
| `Client__c` | Lookup → Client\_\_c |
| `ConfidoId__c` | Text(36), unique |
| `SyncErrorMessage__c` | Long Text |
### PaymentLink\_\_c
| Field | Description |
| :--------------------- | :----------------------------------------------------- |
| `Name` | Descriptive name |
| `BankAccount__c` | Lookup → BankAccount\_\_c (destination) |
| `Client__c` | Lookup → Client\_\_c |
| `Matter__c` | Lookup → Matter\_\_c (optional) |
| `ConfidoId__c` | Text(36), unique |
| `OutstandingAmount__c` | Currency, amount still owed |
| `PaidAmount__c` | Currency, amount paid so far |
| `Status__c` | Picklist: `unpaid`, `partially_paid`, `paid`, `sealed` |
| `Url__c` | URL, the shareable payment link |
| `SyncErrorMessage__c` | Long Text |
### Transaction\_\_c (auto-created via webhooks)
| Field | Description |
| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Amount__c` | Currency |
| `AmountRefunded__c` | Currency |
| `BankAccount__c` | Lookup → BankAccount\_\_c |
| `Client__c` | Lookup → Client\_\_c |
| `Matter__c` | Lookup → Matter\_\_c |
| `PaymentLink__c` | Lookup → PaymentLink\_\_c |
| `ConfidoId__c` | Text(36), unique |
| `CreatedOn__c` | DateTime |
| `ErrorMessage__c` | Text(255) |
| `LastFour__c` | Text(255), last 4 of payment method |
| `Method__c` | Picklist: `ACH`, `CARD`, `CREDIT`, `DEBIT`, `MANUAL`, `PUSH_TO_CARD` |
| `PayerEmail__c` | Email |
| `PayerName__c` | Text(255) |
| `Status__c` | Picklist: `PENDING`, `SUCCESSFUL`, `DEPOSITED`, `FUNDS_IN_TRANSIT`, `REFUNDED`, `PARTIALLY_REFUNDED`, `RETURNED`, `VOIDED`, `ERROR`, `CHARGED_BACK`, `HELD` |
| `Type__c` | Text(255), e.g. `payment`, `refund`, `return` |
### BankAccount\_\_c (read-only from Confido)
| Field | Description |
| :------------- | :--------------------- |
| `Name` | Nickname |
| `Category__c` | `operating` or `trust` |
| `ConfidoId__c` | Text(36), unique |
| `IsDefault__c` | Checkbox |
| `LastFour__c` | Text(255) |
### AggregateLink\_\_c
| Field | Description |
| :-------------------- | :-------------------------------- |
| `Name` | Descriptive name |
| `ConfidoId__c` | Text(36), unique |
| `PaymentLinkIds__c` | Long Text, comma-separated SF IDs |
| `Url__c` | URL |
| `SyncErrorMessage__c` | Long Text |
### AggregateLinkPaymentLink\_\_c
Junction object (Master-Detail on both sides), managed automatically.
### Platform Events
`WebhookPayload__e` and `TransactionPayload__e` — used internally for async webhook processing.
***
## Invocable Flow Actions
All available in Flow Builder under Action elements. They create/update both the Salesforce record and the corresponding Confido record.
### Create Client (`ClientCreate`)
* **Input:** `name` (String, required)
* **Returns:** `List`
### Update Client (`ClientUpdate`)
* **Input:** `clientId` (String, required), `name` (String, optional)
* **Returns:** `List`
### Create Matter (`MatterCreate`)
* **Input:** `clientId` (String, required), `name` (String, required)
* **Returns:** `List`
### Update Matter (`MatterUpdate`)
* **Input:** `matterId` (String, required), `name` (String, required)
* **Returns:** `List`
### Create Payment Link (`PaymentLinkCreate`)
* **Input:** `amount` (Decimal, required), `bankAccountId` (String, required), `clientId` (String, required), `matterId` (String, optional), `paymentLinkName` (String, optional)
* **Returns:** `List`
### Update Payment Link (`PaymentLinkUpdate`)
* **Input:** `paymentLinkId` (String, required), `amount` (Decimal), `bankAccountId` (String), `clientId` (String), `matterId` (String) — all optional except ID
* **Returns:** `List`
### Create Aggregate Link (`AggregateLinkCreate`)
* **Input:** `paymentLinkIds` (List\, required), `aggregateLinkName` (String, optional)
* **Returns:** `List`
### Update Aggregate Link (`AggregateLinkUpdate`)
* **Input:** `aggregateLinkId` (String, required), `paymentLinkIds` (List\, required — pass empty to delete)
* **Returns:** `List`
### Sync Bank Accounts (`BankAccountsSync`)
* **Input:** None
* Fetches all bank accounts from Confido and upserts/deletes `BankAccount__c` records to match.
***
## Lightning Web Components
All exposed to App Pages, Home Pages, and Flow Screens.
| Component | Description |
| :--------------------------- | :------------------------------------------------------------------------------------------- |
| `confidoSettings` | Settings card |
| `confidoClientCreate` | Client creation form (Client Name) |
| `confidoMatterCreate` | Matter creation form (Client picker + Matter Name) |
| `confidoPaymentLinkCreate` | Full payment link form (Name, Bank Account, Client, Matter, Amount). Displays resulting URL. |
| `confidoAggregateLinkCreate` | Aggregate link from multiple payment links (multi-select with pill container) |
| `confidoAggregateLinkUpdate` | Update an aggregate link's payment links |
| `confidoBankAccountsSync` | "Sync Bank Accounts" button, shows count of synced accounts |
| `confidoTransactionsList` | Read-only table of 10 most recent transactions |
***
## Webhooks & Automatic Transaction Sync
### Flow
```
Confido webhook
→ WebhookListener (HMAC verify)
→ WebhookPayload__e
→ Handler fetches full transaction from API
→ TransactionPayload__e
→ Upserts Transaction__c + auto-creates related Client/Matter/PaymentLink if needed
```
### Supported Events
| Event | Behavior |
| :------------------------------- | :------------------------------------ |
| `transaction.created` | Creates/updates the transaction |
| `transaction.deposited` | Updates status |
| `transaction.funds_in_transit` | Updates status |
| `transaction.ach_returned` | Updates original + creates return txn |
| `transaction.refunded` | Updates original + creates refund txn |
| `transaction.partially_refunded` | Updates original + creates refund txn |
| `transaction.voided` | Updates status |
> **Note:** Payout-type transactions and zero-amount transactions (e.g., trust surcharging) are auto-skipped.
***
## Trigger Sync Behavior
| Object | Behavior |
| :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Client__c` | After Insert/Update: auto-syncs to Confido if no `ConfidoId` or Name changed |
| `Matter__c` | After Insert/Update: same pattern |
| `PaymentLink__c` | After Insert/Update: syncs if no `ConfidoId` or amount/bank/client/matter changed. Before/After Delete: removes from aggregate links then from Confido |
| `AggregateLink__c` | After Insert/Update: syncs + re-creates junction records |
Sync errors are stored in `SyncErrorMessage__c` and cleared on the next successful sync.
***
## Quick Start Checklist
1. ☐ Install `confido_core` package
2. ☐ Set API Token in Custom Settings
3. ☐ Set Webhook Secret in Custom Settings
4. ☐ Check/uncheck Production Environment
5. ☐ Verify Remote Site Settings are active
6. ☐ Configure webhook URL in Confido
7. ☐ Expose `/webhook` endpoint via SF Site or Experience Cloud
8. ☐ Run Sync Bank Accounts
9. ☐ Create first Client, Matter, and Payment Link
10. ☐ Verify transactions flow in via webhooks
***
## Dependencies
* **Nebula Logger** — Package uses `Nebula.Logger` for logging. Install from: [https://github.com/jongpie/NebulaLogger](https://github.com/jongpie/NebulaLogger)
# Firm Declined Status
Source: https://docs.confidolegal.com/upcoming-changes/firm-declined-status
Release Date: January 15th, 2026
We are introducing a new `FirmStatus.DECLINED` enum value to the `firm.status` field. A firm will be assigned this status if it does not meet our underwriting requirements.
`FirmStatus.DECLINED` is a terminal state. By the time a firm reaches this status, our underwriting team has already requested follow-up documentation and the firm has still not satisfied the required criteria. To restart the application process, please contact us at [support@confidolegal.com](mailto:support@confidolegal.com).