# Customer Portal Changelog
Source: https://docs.grailpay.com/changelog/customer-portal-changelog
### What's new:
This release includes routine dependency updates across the platform and introduces password
reset functionality with SendGrid email delivery for the Customer Portal.
### Why it matters:
Keeping dependencies up to date ensures the platform remains secure, stable, and compatible
with the latest improvements from upstream libraries. The addition of password reset support
with a reliable email delivery provider improves the admin experience and account security.
### Highlights:
* Updated over 30 packages across the platform, including Laravel, Symfony, Livewire, and security-related libraries
* Added password reset functionality for the Customer Portal with SendGrid as the mail driver
* Fixed an issue where admin password resets failed due to an undefined route
### Release Features
* Updated consumer integration to support the new v3 onboarding response format for Person and Business,
ensuring consistent data handling and UI display.
### Release Feature
* **V3 API Response Standardization:** All V3 APIs of List and Showing (Person, Business, and Merchant) now follow a unified response contract with consistent structure. Error responses are now standardized across all endpoints, providing clearer, more consumer-friendly messages with proper attribute naming in validation errors.
* **Enhanced Relational Data:** We've introduced a new relations object across all V3 endpoints, making it easier to understand entity relationships:
* Person responses now include related Business or Merchant UUIDs.
* Business and Merchant responses include associated Person UUIDs.
### Maintenance Items
Billing Improvements: Fixed filter inconsistencies in Billing Item and Summary endpoints, ensuring accurate and reliable query results across both endpoints with consistent behavior.
# Payments API Changelog
Source: https://docs.grailpay.com/changelog/payments-api-changelog
### What's new:
GrailPay now supports standalone KYC verification for persons, independent of any merchant or
business onboarding flow. A new API allows clients to verify individuals directly, with support
for both GrailPay-managed and externally provided verification.
### Why it matters:
Previously, KYC verification was only available as part of the merchant onboarding process. With
standalone Person KYC, clients who need to verify individuals outside of a business context—or
ahead of onboarding—can now do so through a dedicated API. This provides greater flexibility in
how and when identity verification is performed while maintaining consistent compliance tracking.
### Highlights:
* New API endpoint for verifying persons independently, without requiring a merchant or business association
* Supports two verification methods: GrailPay-managed verification (via OneFootprint) or externally verified data provided by clients or partners
* Intelligent duplicate detection using identity details such as TIN and personal information
* KYC results are automatically shared with the Identity service for connected users
* Compliance status updates are processed consistently, ensuring accurate tracking and reliable notifications with no disruption to existing integrations
### What's new:
GrailPay has made significant improvements to how payments are created and processed. The system
now responds faster, handles errors more gracefully, and provides greater visibility into payment
status—all without impacting the experience for end users or merchants.
### Why it matters:
Payment processing reliability is the foundation of any payments platform. These improvements
ensure that payments move through the system more predictably, recover automatically from
temporary failures, and provide clear audit trails for operations and support teams. For
integration partners, faster API responses and better error messaging mean a smoother
development experience.
### Highlights:
* Payment creation now returns an immediate response—processing is handled asynchronously in the background for a noticeably faster experience
* Payments follow a clearer lifecycle: created → queued → processed, making it easier to track status at any point
* Failed processing attempts are automatically retried in the next available window, with each attempt logged with a reason and timestamp
* Processing limits are checked before submission—payments wait for the next window rather than failing outright
* Stronger upfront validation ensures payer and payee are valid, active, compliant, and have the correct bank accounts before a payment is created
* Cancelled or paused payments are handled correctly in the queue—cancelled payments will not proceed, and resumed payments pick up where they left off without duplicates
* Updated API documentation with a full list of possible error messages and their meanings
* Improved system logging and monitoring to reduce edge-case issues in production
### What's new:
This release resolves an issue that prevented clients from subscribing to the `NocProcessed`
webhook event.
### Why it matters:
The `NocProcessed` event is essential for receiving real-time notifications when a Notification
of Change (NOC) has been processed. This fix ensures that the event can be successfully registered
during webhook setup.
### Highlights:
* Fixed an issue where the `NocProcessed` webhook event was rejected as invalid during registration despite being a
supported event type
### What's new:
GrailPay now supports onboarding Beneficial Owners using an Individual Tax Identification Number
(ITIN) in place of a traditional Social Security Number (SSN).
### Why it matters:
Not all beneficial owners have a Social Security Number. By accepting ITINs, GrailPay enables a
broader range of legitimate business owners to complete onboarding and pass KYC verification—
expanding access to the platform while maintaining compliance standards.
### Highlights:
* Beneficial Owners can now be onboarded using an ITIN (tax IDs in the 900–999 range)
* ITIN values are validated against IRS formatting rules, with clear error messaging for invalid entries
* Updated SSN validation messaging to accurately reflect accepted formats
### What's new:
Phase 2 of the KYB Compliance Refactor and Beneficial Owner Migration is now complete. Merchant
compliance is no longer stored as a static flag—it is now dynamically computed based on the latest
KYB result from verification providers and the KYC results of all associated Beneficial Owners.
A new `compliance_status` object and `ComplianceStatusChanged` webhook event have been introduced
to give you full visibility into a Merchant's compliance posture.
### Why it matters:
With compliance status now computed in real time, approval decisions always reflect the most current
verification data. This eliminates the risk of stale or outdated compliance states and ensures that
only Merchants with approved KYB and fully verified Beneficial Owners can transact. The new
`compliance_status` object consolidates KYB and KYC information into a single view, and the
`ComplianceStatusChanged` webhook keeps you informed the moment anything changes.
### Highlights:
* Merchant compliance is now dynamically computed—a Merchant is approved only when KYB status is `approved` and all Beneficial Owners have approved KYC statuses
* Merchants processed by third parties are recorded with a status of `approved_externally`
* New `compliance_status` object added to V3 API Merchant responses and webhook payloads, showing the latest KYB status and KYC status of each Beneficial Owner
* New `ComplianceStatusChanged` webhook event fires whenever KYB or KYC status changes — **all clients must subscribe to this event**
* Updates to Merchant or Beneficial Owner data automatically retrigger compliance checks
* All legacy compliance logic and deprecated approval handling have been removed
### What's new:
GrailPay now provides full merchant status management and compliance enforcement, giving you
direct control over merchant activation and ensuring only active, compliant merchants can
process transactions.
### Why it matters:
Managing merchant eligibility is essential for reducing risk and maintaining regulatory compliance.
With dedicated activation and deactivation endpoints—combined with automatic compliance
validation—you can confidently control which merchants are allowed to transact, with full
transparency into status changes and block reasons.
### Highlights:
* New Activate and Deactivate Merchant API endpoints with optional deactivation reason support
* Merchants have a clear `active` / `inactive` status, with deactivation reasons and timestamps recorded
* Compliance enforcement ensures only merchants with an approved compliance status can process transactions
* Transactions are blocked with clear error responses when a merchant is inactive or non-compliant
* Both conditions—active status and approved compliance—must be met before a transaction is allowed
### What's new:
This release improves error messaging when attempting to delete a bank account that is not yet
eligible for deletion.
### Why it matters:
Clear and actionable error messages help you understand why a request was rejected and what
conditions must be met before proceeding—reducing confusion and support overhead.
### Highlights:
* Improved error messaging for bank account deletion requests when the account is not yet eligible for removal
### What's new:
Clawback APIs are now available in GrailPay V3, providing programmatic access to view and track
clawbacks alongside your other financial resources.
### Why it matters:
Clawbacks are a critical part of payment operations, and until now lacked dedicated API support in V3.
With these new endpoints, you can retrieve and monitor clawbacks with the same consistency and
reliability you expect from the rest of the GrailPay API—streamlining reconciliation and reporting.
### Highlights:
* New V3 Clawback API endpoints for viewing and tracking clawbacks
* Consistent resource structure aligned with other V3 financial APIs
* Enables programmatic access for improved reconciliation and reporting workflows
### What's new:
The V3 Account Validation API now supports multiple versions of Account Intelligence, giving you
the flexibility to choose between V1, V2, or V3 intelligence models when validating bank accounts
and routing numbers.
### Why it matters:
Each version of Account Intelligence offers different levels of insight and decisioning capability.
With version selection, you can adopt newer models at your own pace—leveraging enhanced risk signals
and validation accuracy when you're ready, while maintaining compatibility with existing integrations.
### Highlights:
* V3 Account Validation endpoint now accepts a version parameter for Account Intelligence
* Choose between V1, V2, or V3 intelligence models per request
* Newer model versions provide enhanced insights and improved validation accuracy
* Full backward compatibility with existing validation workflows
### What's new:
GrailPay now automatically falls back from FedNow to Fast ACH (same-day) when a FedNow payout fails,
ensuring funds are still delivered without delay.
### Why it matters:
FedNow payouts can occasionally fail due to network issues or recipient bank limitations. Rather than
requiring manual intervention or leaving a payout in a failed state, the platform now automatically
retries via Fast ACH—keeping your payment operations running smoothly and your vendors paid on time.
### Highlights:
* Automatic failover from FedNow to Fast ACH (same-day) for failed individual payouts
* Ensures uninterrupted fund delivery without manual intervention
* Failover is seamless and requires no action on your end
### What's new:
The Bank Link flow has been enhanced to ensure reliable OAuth redirection for mobile applications.
### Why it matters:
During bank linking, users are redirected to their bank's OAuth flow to authorize account access.
Previously, mobile applications could experience session loss after completing OAuth, interrupting
the linking process. This fix ensures a seamless return to the Bank Link flow after authorization.
### Highlights:
* Improved OAuth redirect handling for mobile Bank Link sessions
* Prevents session loss after OAuth completion, allowing the bank linking process to continue without interruption
### What's new:
Processor payouts exceeding \$1,000,000 are now automatically split into multiple smaller payouts to
ensure successful processing without hitting banking or partner-imposed limits.
### Why it matters:
Large processor payouts can fail or be rejected when they exceed thresholds set by banking partners.
Automatic splitting removes this risk, ensuring high-value payouts process reliably without requiring
manual intervention.
### Highlights:
* Processor payouts exceeding \$1M are automatically divided into multiple payouts, each below the threshold
* Applies to both ACH and FedNow processor payouts
* Processing is seamless with no action required on your end
### What's new:
This release includes an update to the Swagger API documentation security schema.
### Why it matters:
Keeping our OpenAPI specification compliant with validation standards ensures a consistent and
warning-free experience when testing authenticated endpoints through the Swagger UI.
### Highlights:
* Updated the Swagger security schema naming to comply with OpenAPI validation rules, eliminating documentation warnings
### What's new:
GrailPay now supports FedNow for processor payouts, along with new webhook events and a standardized
modality response object to give you full visibility into how processor-level payouts are processed.
### Why it matters:
Processor payouts can involve high volumes of batched transactions, making transparency into their
status and payment rail critical. These additions enable real-time tracking of processor payout
lifecycles and provide clear insight into whether funds were sent via ACH or FedNow—improving
reconciliation and confidence in your payout operations.
### Highlights:
* FedNow is now supported as a payment rail for processor payouts
* New webhook events for processor payout lifecycle tracking:
* `ProcessorPayoutCreated`
* `ProcessorPayoutCompleted`
* `ProcessorPayoutFailed`
* A `modality` object is now included in batch payout responses, indicating the payment rail (ACH or FedNow) and processing speed
* Historical payout records have been backfilled with modality data for full consistency
### What's new:
This release resolves issues with our Swagger API documentation that were causing validation failures.
### Why it matters:
Accurate and reliable API documentation is essential for a smooth integration experience. These fixes
ensure the Swagger UI remains a dependable reference for building against the GrailPay API.
### Highlights:
* Resolved OpenAPI specification issues that were causing Swagger validation failures in production
* API documentation now passes all validation checks, ensuring accuracy for developers and integrators using the Swagger UI
### What's new:
This release includes fixes to improve the developer experience when working with the GrailPay API.
### Why it matters:
These updates reduce friction when testing API endpoints and submitting data, helping you integrate
more quickly and avoid common validation errors.
### Highlights:
* Fixed an issue where Swagger UI was not correctly passing the `Authorization: Bearer` header on authenticated requests — tokens entered in the Authorize section now work as expected
* Improved phone number validation messaging to clearly indicate that values must contain exactly 10 digits without the `+1` prefix or special characters
### What's new:
GrailPay now supports FedNow payouts, enabling instant, around-the-clock payout processing for eligible
transactions. Clients approved for FedNow will automatically receive payouts via the FedNow network once their
associated transaction debits have settled.
### Why it matters:
Traditional ACH payouts are limited to banking hours and standard processing windows. With FedNow support,
your vendors can receive funds in real time—24 hours a day, 7 days a week—reducing settlement delays and
improving cash flow for your payment operations.
### Highlights:
* Instant payouts via the FedNow network for enabled vendors
* True 24/7 payout processing with no dependency on banking hours
* Automatic routing ensures FedNow-eligible transactions are processed separately from standard ACH payouts
### What’s new:
We’ve introduced three new `Person` statuses and a new webhook event to improve visibility into why a person may
be placed on hold.
### Why it matters:
These additions provide clearer insight into hold reasons—whether related to sanctions or suspected fraud—so you can
take appropriate action and maintain compliance. The new webhook event ensures real-time notification when a person’s
status changes.
### Highlights:
* New person statuses:
* `ON_HOLD_SANCTIONS`
* `ON_HOLD_FRAUD_PAYER`
* `ON_HOLD_FRAUD_PAYEE`
* New `PersonStatusChanged` webhook event triggers whenever a person enters or exits one of these statuses
* Enhances transparency and traceability for risk-related holds
### What’s new:
We’ve added full transaction lifecycle management to the GrailPay API with three new V3 endpoints: **Pause**,
**Resume**, and **Cancel**.
### Why it matters:
These new controls give vendors and processors more flexibility and control over transaction processing. Whether
halting a transaction for compliance review or canceling a transfer in progress, this release adds powerful
capabilities with complete auditability and real-time notifications.
Please view our updated [Manage Transactions](/docs/technical/transactions/manage-transactions) guide for full details on using
these new endpoints and understanding their behavior.
### Highlights:
* **New V3 endpoints** for pausing, resuming, and canceling transactions
* **New transaction statuses**: `PAUSED` and `AWAITING_CANCELLATION`, with full timestamp tracking
* **Pause** halts the ACH processing for a transaction in progress
* **Resume** re-initiates ACH or syncs with bank state depending on prior progress
* **Cancel** either halts the transaction immediately or initiates a reverse payout if debit has been settled
* **Three new webhook events**: `TransactionPaused`, `TransactionResumed`, and `TransactionAwaitingCancellation`
### What’s new:
We’ve fixed an issue where the `MerchantUpdated` webhook event was not being triggered when a merchant transitioned
to the `IN_REVIEW` state.
### Why it matters:
Merchants moving into review—either due to failed KYC or updates from Middesk—will now properly emit webhook
notifications. This ensures platforms receive real-time updates and can accurately reflect onboarding status in
their workflows.
### Highlights:
* Fixed bug preventing `MerchantUpdated` webhook from triggering on `IN_REVIEW` transitions
* Webhooks now trigger on KYC failure when KYB status moves to `IN_REVIEW`
* Middesk-originated `IN_REVIEW` changes also emit update notifications
### What’s new
No new features were released in this update.
### Why it matters
We’ve made backend improvements to ensure data integrity and consistency across V3 listing endpoints, especially
when handling relational data like linked bank accounts.
### Highlights
* Fixed binding issues between collection objects and resource transformers
* Ensured relational data (e.g., bank accounts) is preserved during transformation
* Added safeguards to prevent data loss during response transformation
### What’s new
We’ve made several enhancements to the V3 API, including unified bank account data across key endpoints and a new
onboarding flow for adding bank accounts to persons.
### Why it matters
These updates give you greater visibility into linked bank accounts, improve onboarding flexibility, and enable
support for additional payment rails like FedNow. You can now onboard and validate accounts via Plaid or manually—all
with consistent structure and improved intelligence.
### Highlights
* **Unified Bank Account Objects:** Bank account details are now included in all V3 listing and detail API responses for
People, Merchants, and Businesses.
* **New V3 Bank Account Onboarding Endpoint:** New v3 API endpoint for adding bank accounts with account intelligence of various versions. This includes:
* Support for both Plaid-based and manual bank account onboarding
* Comprehensive account validation including account holder name verification
* Duplicate detection for manual accounts
* **Cleaner Timestamp Responses:** Removed unused updated\_at fields for a more streamlined response format.
### What’s new:
We’ve added a new status field to all Person-related V3 API responses, including List, Fetch, Onboard, and Update,
as well as the PersonCreated and PersonUpdated webhook event payloads.
### Why it matters:
You can now immediately see the current status of any user in your system. This makes it easier to confirm that a
person is in a valid state to transact before initiating a transfer, helping reduce failed attempts and streamline your integration logic.
### Highlights:
* New status parameter added to V3 API responses for People (List, Fetch, Onboard, Update)
* Included in webhook payloads for both person creation and updates
* Improves visibility into user state and transactional readiness across the platform
### What’s new:
We’ve added a new `kyb_rejected_reason` field to both the Merchant Create and Merchant Update API responses, as
well as the corresponding MerchantCreated and MerchantUpdated webhook event payloads.
### Why it matters:
You can now see the specific reason a merchant’s KYB (Know Your Business) verification failed. This provides greater
transparency and helps streamline your onboarding and support workflows.
#### Highlights:
* New `kyb_rejected_reason` parameter added to API responses ( Onboard, Update, List, Fetch )
* Included in webhook payloads for both merchant creation and updates
* Ensures consistent, actionable KYB feedback across the API and webhooks
### Maintenance Items
* **OpenAPI Documentation Updates:** Fixed an issue with the Webhook Registration OpenAPI documentation to specify
that the `webhook_url` field should be passed as an array to allow for multiple webhook URLs.
### Release Features
* **Optimized Business & Merchant Response Payloads:**
* Responses: Removed full Person objects from create and update responses
* Relations Object: Responses now include a clean relations object containing only the Person UUID
* **OpenAPI Documentation Updates:** Updated OpenAPI documentation for Business and Merchant onboarding and update endpoints
to reflect the new relational data structure.
### Maintenance Items
* **Vendor Object in Relations:** Resolved an issue where the vendor object was incorrectly appearing in List
Person and List Merchant API responses. The relations attribute now correctly returns only allowed UUID references,
ensuring consistency across all V3 endpoints.
### Release Features
* **V3 API Response Standardization:** All V3 APIs of List and Showing (Person, Business, and Merchant) now follow a
unified response contract with consistent structure. Error responses are now standardized across all endpoints,
providing clearer, more consumer-friendly messages with proper attribute naming in validation errors.
* **Enhanced Relational Data:** We've introduced a new relations object across all V3 endpoints, making it easier to
understand entity relationships:
* Person responses now include related Business or Merchant UUIDs.
* Business responses include the associated Person UUID.
* Merchant responses include the associated Person UUID.
* **Webhook Enhancements:** Updated Person and Merchant webhook responses to include the new relations object structure,
providing cleaner, more relevant information for integrations.
### Maintenance Items
* **Billing Improvements:** Fixed filter inconsistencies in Billing Item and Summary endpoints, ensuring accurate and
reliable query results across both endpoints with consistent behavior.
# Risk Intelligence API Changelog
Source: https://docs.grailpay.com/changelog/risk-api-changelog
### Maintenance Items
* **OpenAPI Documentation Updates:** Added all the necessary changes to accurately document the Transaction
Intelligence endpoint.
### Maintenance Items
* **OpenAPI Documentation Updates:**
* Added all the necessary changes to accurately document the V3 Account Intelligence endpoint.
* Marked all of the V1 and V2 Account Intelligence endpoints as deprecated.
# ACH Processing Windows & Settlement Timing
Source: https://docs.grailpay.com/docs/resources/ach-processing-windows
## Overview
In planning settlement timing of an ACH to payees, it should be understood that the exact timing is subject to updates
from multiple participating financial institutions, and as such is not fully controllable and cannot be guaranteed.
GrailPay orchestrates file delivery according to preset windows, and there is then flexibility within these windows for
participating financial institutions to action and communicate updates.
Numerous factors influence the exact timing of events on participant bank accounts and funds availability, including the
posting policies of destination financial institutions, which GrailPay cannot alter or influence.
GrailPay delivers ACH files to our banking partner according to a preset schedule based on the Federal Reserve's preset
ACH windows. Our file delivery batches Standard and Fast ACHs, which our banking partner then distributes based on the
set speed of each individual payment within its own file distribution schedule to the Fed.
***
## Processing Windows
| Window | Recommended Submission to GrailPay | GrailPay to Bank File Transmission | Bank to Fed File Transmission | Fed to Bank File Update Expected |
| ---------------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ----------------------------- | -------------------------------- |
| First Fast ACH | 9:00am ET | 9:15am ET | 10:30am ET | 1:30pm ET |
| Second Fast ACH | 1:00pm ET | 1:15pm ET | 1:30pm ET | 5:00pm ET |
| Third Fast ACH | 2:30pm ET | 2:45pm ET | 3:00pm ET | 6:00pm ET |
| Standard ACH | 5:15pm ET | 5:30pm ET | 6:00pm ET | 8:30am ET next day |
| Instant Payments | Instant payout upon settlement of the first-leg ACH debit, or prefund an account for instant payouts upon API call. | | | |
The exact timing of status changes on Fast ACH payments typically occurs anytime within the window from the Bank to
Fed File Transmission time up until the Fed to Bank File Update Expected time. While status changes on individual
payments submitted concurrently to the same window on any given day are typically consistent, the exact timing of
status changes within a given window can vary from day to day, sometimes considerably.
***
## End-to-End Payment Settlement
End-to-end settlement covers the full path from payment creation through to funds reaching the payee — the debit on
the payer all the way through to the credit to the payee.
A payout is preceded by a first-leg ACH debit — the capture, or pay-in — and the payout to the payee typically follows
once that capture has settled. Where an account is prefunded, a payout may be released without first waiting on a
capture.
Because each leg moves through the processing windows above, expected end-to-end timing depends on the speed of the
payment and on when it is created relative to those windows. As with the windows themselves, the timings below reflect
what we generally expect rather than a guaranteed schedule, and remain subject to the posting policies and actions of the
participating financial institutions.
GrailPay offers pre-funded payout flows on a per-request basis.
| Speed | Expected End-to-End Payout Settlement Timing |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Standard ACH | Generally T+4 — the capture settles the following business day, a two-day payout delay applies, and the payout then settles. |
| Fast ACH (same / next day) | Same day or next day, depending on the time of creation relative to the fast windows. |
| Instant | Real time — upon settlement of the first-leg capture, or immediately from a prefunded balance. |
***
## Individual Payouts
An individual payout is picked up at the next applicable processing window following its creation.
| Speed | Flow of Funds | Expected Payout Settlement |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------- |
| Standard ACH | Transmitted in the 5:30pm ET window; the capture settles the following business day, followed by a two-day payout delay before the payout runs through the same process. | T+4 |
| Fast ACH (same / next day) | If created in time for the capture to settle — generally by around 1:00pm ET — the payout is released in the final fast window the same day. | Same day if created in time; otherwise next day |
| Instant | Released in real time, either on settlement of the first-leg capture into GrailPay's FBO, or immediately from a prefunded balance. | Real-time |
***
***
***
## Batch Payouts
Batched payouts group the pay-ins of many payments from many payers to one payee into one payout, thereby consolidating
cost to you and reconciliation line items for the payee.
| Speed | Flow of Funds | Expected Payout Settlement |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| Standard ACH | All Standard payments collect into the 5:30pm ET file and follow the same path as an individual payout; batching makes no difference to timing. | Generally around T+4 |
| Fast ACH (same / next day) | Batched pay-ins collect at the final fast window of the day and are released into a payout the following morning. | Next day |
| Instant | Generally funded from a prefunded balance and released in real time; where not prefunded, release follows settlement of the funding capture. | Real-time |
***
***
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Compliance Status Overview
Source: https://docs.grailpay.com/docs/resources/compliance-status-overview
Before a Merchant can process transactions through the GrailPay Payments API, they must meet our compliance requirements.
Compliance Status represents whether a Merchant is eligible to transact based on the verification of their business and
the individuals who own or control it.
When you onboard a Merchant onto the GrailPay platform, we perform identity verification checks to ensure the business
is legitimate and that we understand who is behind it. This is a regulatory requirement for all payment processors and
helps protect against fraud, money laundering, and other financial crimes.
A Merchant's Compliance Status is determined by two factors:
* **KYB (Know Your Business)** — Verification of the Merchant's business entity
* **KYC (Know Your Customer)** — Verification of each Beneficial Owner associated with the business
Both the Merchant and all of its Beneficial Owners must pass verification before the Merchant can begin transacting.
***
## What is KYB?
To comply with U.S. financial regulations and ensure the integrity of our Payments platform, GrailPay requires all Merchants
to pass Know Your Business (KYB) verification as part of the onboarding process. KYB is a mandatory due diligence process
that helps identify and validate the legitimacy of a business, its ownership structure, and its associated individuals.
During KYB, we collect and validate details such as:
* Legal business name and entity type
* Employer Identification Number (EIN / TIN)
* Business address
* Business owners (with associated SSNs, emails, and dates of birth)
* Supporting documentation, when applicable
This information is submitted to our verification providers to confirm that the business is registered, operating
legitimately, and not subject to regulatory flags.
### Why KYB Matters
KYB is a core component of anti-money laundering (AML) and fraud prevention frameworks. By verifying Merchant entities
during onboarding, GrailPay ensures:
* Trustworthy participation in the ACH network
* Reduced risk of fraudulent or high-risk businesses
* Compliance with NACHA, FinCEN, and other regulatory obligations
* Accurate ACH classification for downstream financial processing
### KYB Outcomes
Once a KYB request is submitted, the Merchant will be assigned one of the following status values:
| Status | Description |
| ----------- | -------------------------------------------------------------------- |
| `pending` | The KYB submission has been received and is awaiting processing. |
| `in_review` | Additional manual review is required before a decision can be made. |
| `approved` | The Merchant has successfully passed KYB verification and is active. |
| `rejected` | The KYB verification failed. A rejection reason will be returned. |
***
## What is KYC?
KYC, or "Know Your Customer," is the process of verifying the identity of individuals associated with a business. In the
context of GrailPay, KYC is performed on each Beneficial Owner of a Merchant.
A Beneficial Owner is any individual who owns 25% or more of the business, or who has significant control over the
business (such as a CEO, CFO, or Managing Member). Federal regulations require us to verify the identity of these
individuals before allowing the business to process payments.
During KYC verification, we collect and verify information such as:
* Full legal name
* Valid email address
* Date of birth
* Social Security Number (SSN) or government-issued ID
* Residential address
This information is checked against authoritative data sources to confirm the individual's identity. KYC verification
helps ensure that the people behind a business are who they claim to be.
### KYC Outcomes
| Status | Description |
| ---------- | ----------------------------------------------------------------------------------------------------- |
| `pending ` | The individual's identity verification process has begun. |
| `approved` | The individual's identity has been verified successfully. |
| `rejected` | The individual's identity could not be verified. The Merchant cannot transact until this is resolved. |
***
## How Compliance Status Works
Compliance Status is evaluated in real-time whenever a Merchant attempts to perform a compliance-gated action, such as
creating a transaction. This ensures that decisions are always based on the most current verification information
available.
Rather than storing a static status that could become outdated, GrailPay computes the Compliance Status dynamically by
examining the current KYB and KYC results. This approach provides:
* **Accuracy** — The status always reflects the latest verification results
* **Transparency** — Compliance decisions are based on clear, documented criteria
* **Auditability** — A complete history of verification events is maintained for regulatory purposes
***
## Compliance Status Values
A Merchant's Compliance Status is one of four values: **Pending**, **Approved**, **In Review**, or **Rejected**.
### Pending
When a Merchant is first onboarded, their Compliance Status is Pending. This indicates that the KYB and/or KYC
verification processes are still underway.
This means:
* The business KYB verification has not yet completed, and/or
* One or more Beneficial Owners have not yet completed KYC verification
Merchants with a Pending status cannot create transactions until all verifications are complete.
### Approved
The Merchant has passed all compliance checks and is authorized to transact.
This means:
* The business has passed KYB verification
* All Beneficial Owners have passed KYC verification
Merchants with an Approved status can create transactions, receive payouts, and use all features of the Payments API
without restriction.
### In Review
The Merchant's KYB verification requires additional review, but all Beneficial Owners have passed KYC.
This means:
* The business verification is pending manual review
* All Beneficial Owners have been verified successfully
Merchants with an In Review status cannot create transactions until the KYB review is complete. Most reviews are
resolved within 1–2 business days. If additional documentation is required, we will reach out to the Merchant directly.
### Rejected
The Merchant has failed one or more compliance checks and is not authorized to transact.
This can happen if:
* The business failed KYB verification
* One or more Beneficial Owners failed KYC verification
Merchants with a Rejected status cannot create transactions or process payments. If you believe a rejection was made in
error, please contact GrailPay support to discuss next steps.
***
## Corrective Action
In the event that either KYB Status or KYC Status is set to In Review or Rejected, manual intervention will be necessary
to unblock the merchant from compliance-gated actions. The exact remediation steps will vary, and may include requests
for additional documentation from the merchant. Please reach out to [support@grailpay.com](mailto:support@grailpay.com)
to highlight any merchants that require immediate attention.
***
## Monitoring Compliance Status
You can monitor a merchant’s Compliance Status by retrieving the merchant record or by listening for the
[ComplianceStatusChanged](/docs/technical/webhooks/events#compliance-status-changed-event) webhook event. Changes to
Compliance Status will be reflected in real time, allowing your system to take appropriate onboarding or operational
actions.
***
## Summary
| Status | KYB Result | KYC Results (All Owners) | Can Transact? |
| ------------- | ---------- | ------------------------ | ------------- |
| **Pending** | Pending | Any Pending | ❌ No |
| **Approved** | Approved | All Approved | ✅ Yes |
| **In Review** | In Review | All Approved | ❌ No |
| **Rejected** | Rejected | — | ❌ No |
| **Rejected** | — | Any Rejected | ❌ No |
A Merchant is Rejected if **either** the business fails KYB **or** any Beneficial Owner fails KYC. Only one failure
is required for rejection.
# Key Terms
Source: https://docs.grailpay.com/docs/resources/key-terms
### ACH
Stands for Automated Clearing House, a network run by Nacha (National Automated Clearing House Association). Since the 1970s, the ACH network has electronically moved money between bank accounts across the United States.
### ACH Credit
Funds moving through the ACH system that are pushed into an account.
### ACH Debit
Funds moving through the ACH system that are pulled out of an account.
### ACH Entry
Used to describe a single transaction submitted to the ACH Network.
### ACH Fees
Fees associated with payment processing, typically ranging between zero to ten dollars per payment, depending on the specific processor.
### ACH Network
The electronic network where ACH Entries are settled between participating depository financial institutions (banks and credit unions).
### ACH Operator
The “Clearing House” where participating depository financial institutions automatically settle ACH Entries. There are only two: The Federal Reserve (FedACH) and The Clearing House (EPN).
### ACH Return Code
One of 85 possible reasons for disputing (or returning) an ACH Entry. Each return submitted by the RDFI must include a return code.
### Authentication
A data security technique used to ensure that the professed sender of information or a payment order is actually who they claim to be.
### Authorization
A written agreement signed by an employee or customer, or an authenticated electronic process, that allows the posting of debits or credits arising from ACH transactions to the employee’s or customer’s account.
### Batch Processing
A method of aggregating multiple ACH Entries into “batch” files before they are transmitted between Third-Party Senders, ODFIs, RDFIs, and the ACH Operator.
### Business (Banking) Day
Any day on which the DFI is open to the public for carrying on substantially all its banking functions.
### Bank Secrecy Act (BSA)
US legislation implemented in 1970 to prevent criminals from using financial institutions to launder money. Institutions must report large transactions, suspicious activity, and maintain compliance with regulatory agencies.
### Business/Company
An individual, sole proprietorship, partnership, or corporation engaged in a commercial enterprise or venture.
### Consumer Account
An account held by a DFI and established by a natural person primarily for personal, family, or household—not commercial—purposes.
### Corporate Credit or Debit (CCD ACH)
An ACH application that transfers funds between corporate accounts for collection or disbursement purposes.
### Direct Deposit
A deposit made to a consumer’s account via ACH. Commonly used for payroll, social security, and pension payments.
### Direct Payments
A charge to a receiver’s account via ACH. Common for insurance, utility, mortgage, and loan payments.
### Effective Entry Date
The date the originator has requested that the entry post to the receiver’s account.
### Electronic Authorization
A digital form of ACH authorization that must be visible, retainable, and verifiable (e.g., PIN, digital signature).
### Electronic Funds Transfer (EFT)
Any transfer initiated via terminal, phone, or computer to debit or credit an account at a financial institution.
### Electronic Signature
An electronic sound, symbol, or process logically associated with an agreement or authorization with the intent to sign.
### Federal Reserve System
The U.S. central bank made up of 12 district banks. All national banks are members; others may opt in.
### File
A group of ACH entries with control totals. A file may contain multiple batches and represents the smallest unit sent or received by the ACH system.
### Financial Crimes Enforcement Network (FinCEN)
A bureau within the U.S. Department of Treasury created to combat money laundering and terrorism financing.
### Know Your Customer (KYC)
A set of due diligence guidelines requiring financial institutions to verify customer identity and assess risk.
### Know Your Business (KYB)
Due diligence process to verify a business’s legitimacy, helping prevent fraud and comply with AML regulations.
### NACHA
National Automated Clearing House Association – the governing body that sets and enforces ACH transaction rules.
### NACHA File Format
Also called ACH file format. A structured plain text file used to execute ACH transfers through NACHA standards.
### Notification of Change (NOC)
A correction sent by a receiving institution that identifies and fixes incorrect transaction data.
### Originating Depository Financial Institution (ODFI)
A financial institution that originates ACH entries on behalf of itself or external clients.
### Originator
The individual, business, or entity that initiates ACH entries.
### Office of Foreign Assets Control (OFAC)
A U.S. agency that enforces economic and trade sanctions to support national security and foreign policy.
### Receiver
The individual or entity that authorizes an ACH credit or debit to an account they own.
### Receiving Depository Financial Institution (RDFI)
The financial institution that receives ACH entries and posts them to its customers' accounts.
### Return / Return Entry
An ACH entry returned to the ODFI by the RDFI with a return reason code.
### Reversing File / Entry (Reversal)
A credit or debit entry that reverses a previously erroneous entry or file.
### Same-Day Entry
A credit ACH entry eligible for same-day processing, settlement, and limited to \$1,000,000 or less.
### Settlement
The movement of funds between institutions to finalize transactions.
### Settlement Date
The date when actual fund transfer occurs, via the Federal Reserve system.
### Settlement Period
The holding period (by ODFI or third party) to allow for returns before final fund release.
### Stop Payment Order
A directive from an account holder to prevent a specific ACH transaction from being paid.
### Trace Number
A unique identifier assigned by the ODFI to each ACH entry for tracking within the network.
### Transaction Code
The two-digit code in an ACH record that identifies the type of account and direction of the transaction.
### Third-Party Sender
An intermediary that processes ACH transactions on behalf of an originator. We act as a Third-Party Sender.
### WEB ACH
An internet-authorized ACH debit from a consumer account. The `WEB` code signifies online initiation.
# Return Codes
Source: https://docs.grailpay.com/docs/resources/return-codes
There are currently **85 distinct ACH return codes**.
While these codes are standardized by **NACHA**, interpretation and enforcement can vary across financial institutions. This means resolving payment failures with return codes may require different communication and actions depending on the financial institutions involved.
***
## Most Common Return Codes
| Code | Title | Account Type | Time Frame | Suggested Customer Message |
| ---- | ---------------------------------------------------- | ------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| R01 | Insufficient Funds | Consumer or Non-Consumer | 2 Banking Days | This transaction has failed due to NACHA code R01, insufficient account funds. Please correct your balance and try again. Retry limit is 2. |
| R02 | Account Closed | Consumer or Non-Consumer | 2 Banking Days | Account is closed. Please update with a valid account. |
| R03 | No Account / Unable to Locate Account | Consumer or Non-Consumer | 2 Banking Days | Unable to find the account. Please check the account details and try again. |
| R04 | Invalid Account Number Structure | Consumer or Non-Consumer | 2 Banking Days | Invalid account structure. Please re-enter the account number and retry. |
| R05 | Unauthorized Consumer Debit using Corporate SEC Code | Consumer | 60 Calendar Days | The transaction was marked incorrectly as corporate (CCD). Use WEB for consumer. |
| R06 | ODFI Requested Return | Consumer or Non-Consumer | Undefined | The bank marked this transaction for return. Please contact the bank before retrying. |
| R07 | Customer Revoked Authorization | Consumer | 60 Calendar Days | Customer has revoked authorization. Contact the customer before retrying. |
| R08 | Payment Stopped | Consumer or Non-Consumer | 2 Banking Days | Payment was stopped. Contact the payor to resolve the issue. |
| R09 | Uncollected Funds | Consumer or Non-Consumer | 2 Banking Days | Funds could not be retrieved. Contact the payor and retry. |
| R10 | Originator not known / not authorized | Consumer; Non-Consumer (ARC, BOC, IAT, POP) | 60 Calendar Days | Payer not authorized. Contact the banks to resolve before retrying. |
| R29 | Not Authorized by Corporate Customer | Non-Consumer | 2 Banking Days | The corporate customer did not authorize the debit. |
***
## Less Common Return Codes
| Code | Title | Account Type | Time Frame |
| ------- | ------------------------------------- | ------------------------ | ------------------ |
| R11 | Not Within Authorization Terms | Consumer; Non-Consumer | 60 Calendar Days |
| R12 | Account Sold to Another DFI | Consumer or Non-Consumer | 2 Banking Days |
| R13 | Invalid ACH Routing No. | Consumer or Non-Consumer | Next File Delivery |
| R14 | Representative Payee Deceased | Consumer or Non-Consumer | 2 Banking Days |
| R15 | Beneficiary / Account Holder Deceased | Consumer | 2 Banking Days |
| R16 | Account Frozen / Returned per OFAC | Consumer or Non-Consumer | 2 Banking Days |
| R17 | File Edit Criteria / Suspicious Entry | Consumer or Non-Consumer | 2 Banking Days |
| R18 | Improper Effective Date | Consumer or Non-Consumer | Next File Delivery |
| R19 | Amount Field Error | Consumer or Non-Consumer | Next File Delivery |
| R20 | Non-Transaction Account | Consumer or Non-Consumer | 2 Banking Days |
| R21 | Invalid Company ID | Non-Consumer | 2 Banking Days |
| R22 | Invalid Individual ID | Consumer or Non-Consumer | 2 Banking Days |
| R23 | Receiver Refused Credit | Consumer or Non-Consumer | Upon Receipt |
| R24 | Duplicate Entry | Consumer or Non-Consumer | 2 Banking Days |
| R25 | Addenda Error | Consumer or Non-Consumer | Next File Delivery |
| R26 | Mandatory Field Error | Consumer or Non-Consumer | Next File Delivery |
| R27 | Trace Number Error | Consumer or Non-Consumer | Next File Delivery |
| R28 | Routing No. Check Digit Error | Consumer or Non-Consumer | Next File Delivery |
| R30 | RDFI not in Check Truncation Program | Consumer or Non-Consumer | Next File Delivery |
| R31 | Permissible Return (CCD/CTX only) | Non-Consumer | Undefined |
| R32 | RDFI Non-Settlement | Consumer or Non-Consumer | Next File Delivery |
| R33 | Return of XCK | Consumer or Non-Consumer | 60 Calendar Days |
| R34 | Limited Participation DFI | Consumer or Non-Consumer | Next File Delivery |
| R35 | Improper Debit | Consumer or Non-Consumer | Next File Delivery |
| R36 | Improper Credit | Consumer or Non-Consumer | Next File Delivery |
| R37 | Source Document Presented | Consumer or Non-Consumer | 60 Calendar Days |
| R38 | Stop Payment on Source Document | Consumer or Non-Consumer | 60 Calendar Days |
| R39 | Improper Source Document | Consumer or Non-Consumer | 2 Banking Days |
| R40–R47 | Enrollment/Identity/Routing Errors | N/A | N/A |
| R50–R53 | RCK-Related Errors | Consumer | 60 Banking Days |
| R61 | Misrouted Return | Consumer | 60 Calendar Days |
| R62 | Erroneous / Reversing Debit | Consumer | 5 Business Days |
| R67 | Duplicate Return | — | — |
| R68–R73 | Timeliness or Routing Issues | Consumer or Non-Consumer | 5 Banking Days |
| R74 | Corrected Return | — | — |
| R75–R77 | Return Disputes / Validity Conflicts | Consumer or Non-Consumer | 2–5 Banking Days |
| R80–R85 | IAT / Foreign / Gateway Errors | Consumer or Non-Consumer | 2 Banking Days |
***
Not all codes require the same resolution path. Work closely with your RDFI and ODFI to ensure compliance when retrying or correcting transactions.
# Scenarios: Direct & Batch Payouts
Source: https://docs.grailpay.com/docs/resources/scenarios-direct-and-batch-payouts
## Direct Payouts
Direct Payouts represent a straightforward, one-to-one funds transfer between two parties. For each transaction initiated
on behalf of a payee, a single corresponding payout is delivered directly to them. This model is ideal for platforms that
need a simple, transparent payment flow where every transaction results in an immediate, individual payout to the recipient.
***
***
***
***
***
***
***
***
## Batch Payouts
Batch Payouts consolidate multiple transactions for a single payee into one combined payout. Rather than issuing a
separate payout for each individual transaction, all debits created on behalf of a payee are rolled up and delivered as
a single credit. This model is ideal for platforms that process a high volume of transactions per payee and prefer to
streamline disbursements into fewer, larger payouts.
***
***
***
# Transaction Queues
Source: https://docs.grailpay.com/docs/resources/transaction-queues
When a transaction is created via the API and passes initial validation, it will return a status of `QUEUED`. This
indicates the transaction has been accepted and entered into GrailPay's processing pipeline.
Transactions in a `QUEUED` state are processed in FIFO (First In, First Out) order based on their `queued_at` timestamp.
No priority is applied based on merchant, transaction type, amount, or modality.
In rare cases, a transaction may be re-queued — for example, if an internal processing error occurs that requires the
transaction to be retried. When this happens, the `queued_at` timestamp is overwritten with the new queue time, so
the transaction's position in the queue is determined by the updated timestamp rather than the original submission time.
This means a re-queued transaction may process after transactions that were submitted later.
## How it works
Once queued, GrailPay handles ACH creation and webhook delivery asynchronously. This means your integration receives a
successful response at transaction creation — processing happens in the background as soon as conditions allow.
## Why transactions may remain queued
A transaction will remain in a `QUEUED` state until it can be processed. Common reasons include:
* **Banking partner unavailability** — The transaction will process as soon as the partner comes back online, in FIFO order.
* **Origination limit reached** — If the 24-hour origination limit has been reached, the transaction will process once the limit rolls over.
Prior to this change, either of the above conditions would result in a transaction creation error.
Transactions are now accepted regardless, eliminating failures caused by partner outages or usage limits.
## Webhooks
You will receive a webhook notification when your transaction moves out of `QUEUED` and begins processing.
See [Transaction Webhook Events](/docs/technical/webhooks/events#transaction-events) for details.
# Transaction States & Events
Source: https://docs.grailpay.com/docs/resources/transaction-states
This page outlines the various **states** a transaction can move through during its lifecycle, with the
corresponding **Event** that appears in API payloads.
To follow the entire movement of funds from a payer to a payee, you will need to monitor both the **main transaction states**
and the **payout states** outlined below.
***
## Main Transaction States
The main transaction states represent the key stages in a transaction’s lifecycle—from the initial debit of funds
from the payer to the final payout to the payee. These states are reflected on the transaction record and indicate the
progression of funds, starting with the capture phase and concluding when the transaction enters the payout (credit)
phase.
| State | Description | Event |
| ----------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `QUEUED` | The transaction has been added to our pipeline for processing. | No Event |
| `CAPTURE_PENDING` | The transaction has been started. | [TransactionStarted](/docs/technical/webhooks/events#transaction-started-event) |
| `CAPTURE_ACH_PENDING` | The debit from the payor is in progress; waiting for deposit into the FBO account. | [TransactionCaptureStarted](/docs/technical/webhooks/events#transaction-capture-started-event) |
| `PAYOUT_PENDING_ACH` | The debit from the payor has been completed and deposited into the FBO account. | [TransactionCompleted](/docs/technical/webhooks/events#transaction-completed-event) |
| `CAPTURE_ACH_FAILED` | The debit from the payor has failed. May occur after the payout has already completed. | [TransactionFailed](/docs/technical/webhooks/events#transaction-failed-event) |
| `PAUSED` | The transaction has been paused | [TransactionPaused](/docs/technical/webhooks/events#transaction-paused-event) |
| `AWAITING_CANCELLATION` | The transaction was requested to be cancelled before the debit has settled. | [TransactionAwaitingCancellation](/docs/technical/webhooks/events#transaction-awaiting-cancellation-event) |
| `CANCELED` | The transaction was cancelled upon request. | [TransactionCanceled](/docs/technical/webhooks/events#transaction-canceled-event) |
| `CLAWBACK` | A clawback was performed due to a failed debit after payout. | [ClawbackComplete](/docs/technical/webhooks/events#clawback-completed-event) |
| `PAYOUT_ACH_PENDING` | The credit process to the payee has started, but the ACH credit has not been created yet. | [PayoutCreated](/docs/technical/webhooks/events#payout-created-event) |
| `IN_PAYOUT` | The transaction has been included in a direct or batch payout. | No Event |
Transactions created via the API that pass initial validation will return a status of `QUEUED`, indicating they have
been accepted and are pending asynchronous processing. To learn more about how queueing works and what to expect,
see [Transaction Queues](/docs/resources/transaction-queues).
### Capture Status States
In our webhooks and API responses, the **capture status** field indicates the current state of the capture phase of a
transaction. This phase involves debiting funds from the payor and depositing them into the FBO account. The capture status
provides insights into the progress of this process, from initiation to completion or failure.
| State | Description | Event |
| ----------------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `QUEUED` | The transaction has been added to our pipeline for processing. | No Event |
| `CAPTURE_PENDING` | The transaction has been started. | [TransactionStarted](/docs/technical/webhooks/events#transaction-started-event) |
| `CAPTURE_ACH_PENDING` | The debit from the payor is in progress; waiting for deposit into the FBO account. | [TransactionCaptureStarted](/docs/technical/webhooks/events#transaction-capture-started-event) |
| `CAPTURE_ACH_FAILED` | The debit from the payor has failed. May occur after the payout has already completed. | [TransactionFailed](/docs/technical/webhooks/events#transaction-failed-event) |
| `CAPTURE_COMPLETE` | The debit from the payor has been completed and deposited into the FBO account. | [TransactionCompleted](/docs/technical/webhooks/events#transaction-completed-event) |
| `PAUSED` | The transaction has been paused | [TransactionPaused](/docs/technical/webhooks/events#transaction-paused-event) |
| `AWAITING_CANCELLATION` | The transaction was requested to be cancelled before the debit has settled. | [TransactionAwaitingCancellation](/docs/technical/webhooks/events#transaction-awaiting-cancellation-event) |
| `CANCELED` | The transaction was cancelled upon request. | [TransactionCanceled](/docs/technical/webhooks/events#transaction-canceled-event) |
***
## Payout States (Credit to Payee)
The payout states represent the stages involved in crediting funds to the payee after a successful capture of funds from
the payor. These states indicate the progress of the payout process, starting from the initiation of the payout to its
completion or failure.
### ACH Payouts
The following states occur during the **payout** phase of a transaction, when funds are being sent to the payee. The
states below are relevant to payouts created using the ACH payment rail.
| State | Description | Event |
| -------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------ |
| `PAYOUT_ACH_PENDING` | ACH credit is in progress; waiting for confirmation. | [PayoutCreated](/docs/technical/webhooks/events#payout-created-event) |
| `PAYOUT_COMPLETE` | Confirmation received that funds were sent to the payee. | [PayoutComplete](/docs/technical/webhooks/events#payout-completed-event) |
| `PAYOUT_ACH_FAILED` | Payout to the payee failed. | [PayoutFailed](/docs/technical/webhooks/events#payout-failed-event) |
### FedNow Payouts
The following states occur during the **payout** phase of a transaction, when funds are being sent to the payee. The
states below are relevant to payouts created using the FedNow payment rail.
| State | Description | Event |
| ------------ | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `PENDING` | The credit to the payee has started, but the Instant credit has not been created yet. | [PayoutCreated](/docs/technical/webhooks/events#payout-created-event) |
| `PROCESSING` | Instant credit is in progress; waiting for confirmation. | — |
| `CANCELED` | The payout has been cancelled. | [PayoutFailed](/docs/technical/webhooks/events#payout-failed-event) |
| `SENT` | The Credit has been sent and is awaiting for the receiving institution to accept the payment | - |
| `DONE` | The credit is complete. | [PayoutComplete](/docs/technical/webhooks/events#payout-completed-event) |
| `ERROR` | Payout to the payee failed due to an error in the transfer. | [PayoutFailed](/docs/technical/webhooks/events#payout-failed-event) |
| `REJECTED` | Payout to the payee failed due to being rejected by the destination institution | [PayoutFailed](/docs/technical/webhooks/events#payout-failed-event) |
| `BLOCKED` | Payout to the payee failed due to being blocked by the destination institution | [PayoutFailed](/docs/technical/webhooks/events#payout-failed-event) |
***
## Sub-Operations
A successful transaction only involves the **capture** and **payout** phases. The following additional flows occur in
edge cases such as errors or refunds.
***
## Reverse Payout States
These occur when a payout fails and funds need to be **returned to the payer**.
| State | Description | Event |
| ----------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `REVERSE_PAYOUT_PENDING` | The reversal credit to the payer has been initiated. | - |
| `REVERSE_PAYOUT_ACH_PENDING` | ACH credit has been picked up by the bank; confirmation is pending. | [ReversePayoutStarted](/docs/technical/webhooks/events#reverse-payout-started-event) |
| `REVERSE_PAYOUT_ACH_COMPLETE` | Confirmation received that funds were returned to the payer. | [ReversePayoutCompleted](/docs/technical/webhooks/events#reverse-payout-completed-event) |
| `REVERSE_PAYOUT_ACH_FAILED` | Reverse payout failed. | [ReversePayoutFailed](/docs/technical/webhooks/events#reverse-payout-failed-event) |
| `REVERSE_PAYOUT_CANCELLED` | The reverse payout has been cancelled. | — |
***
## Clawback
A **clawback** is initiated if the debit from the payor is reversed *after* funds were already sent to the payee.
| State | Description | Event |
| ---------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `CLAWBACK_PENDING` | The Clawback has been created within the GrailPay system and is awaiting the creation of debit from the payee | [ClawbackStarted](/docs/technical/webhooks/events#clawback-started-event) |
| `CLAWBACK_ACH_PENDING` | Bank is processing the debit from the payee; waiting for deposit into the FBO account. | [ClawbackCaptureStarted](/docs/technical/webhooks/events#clawback-capture-started-event) |
| `CLAWBACK_COMPLETE` | The clawback completed successfully; funds returned to the FBO account. | [ClawbackCompleted](/docs/technical/webhooks/events#clawback-completed-event) |
| `CLAWBACK_ACH_FAILED` | The clawback ACH failed. | [ClawbackFailed](/docs/technical/webhooks/events#clawback-failed-event) |
| `CLAWBACK_CANCELLED` | The clawback has been cancelled. | |
***
## Refund
Refunds are **initiated post-transaction** to return funds to the original payer via the
[Refund a Transaction API](/docs/technical/refunds/refund-a-transaction).
| State | Description | Event |
| ---------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `REFUND_PENDING` | Refund requested from payee to payer. | [RefundPending](/docs/technical/webhooks/events#refund-pending-event) |
| `REFUND_CAPTURE_ACH_PENDING` | Debit from payee to FBO is in progress. | [RefundCaptureStarted](/docs/technical/webhooks/events#refund-capture-started-event) |
| `REFUND_ACH_PENDING` | ACH credit to payer initiated. | [RefundCaptureCompleted](/docs/technical/webhooks/events#refund-capture-completed-event), [RefundPayoutPending](/docs/technical/webhooks/events#refund-payout-pending-event) |
| `REFUND_COMPLETE` | Refund has completed successfully. | [RefundPayoutCompleted](/docs/technical/webhooks/events#refund-payout-completed-event) |
| `REFUND_FAILED` | The refund process failed. | [RefundCaptureFailed](/docs/technical/webhooks/events#refund-capture-failed-event), [RefundPayoutFailed](/docs/technical/webhooks/events#refund-payout-failed-event) |
| `REFUND_CANCELLED` | Refund has been cancelled | No Event |
***
# Authentication
Source: https://docs.grailpay.com/docs/risk/authentication
How to authenticate with the Risk Intelligence API using JWT-based credentials.
The Risk Intelligence API uses **JWT-based authentication**. Clients authenticate using a `client_id` and
`client_secret` to obtain an **access token**, which is then used to authorize all subsequent API requests.4
The `client_id` and `client_secret` will be provided by our customer support team upon onboarding.
***
### Integration Steps
#### 1. Access Token Creation
Clients initiate authentication by sending a `POST` request to the `/v1/auth` endpoint with their `client_id`
and `client_secret`.
If successful, the response includes:
* `access_token` — a short-lived token used for authenticating API requests
* `refresh_token` — used to renew the access token without re-authenticating
#### 2. Access Token Refresh
When the access token expires, clients can request a new one by sending a `POST` request to `/v1/auth/refresh` with a
valid `refresh_token`.
#### 3. Usage in API Requests
Include the access token in the `Authorization` header of every API request: `Authorization: Bearer `
***
### 📈 Rate Limit
To ensure fair usage and system stability, the API enforces rate limits.
* If a client exceeds the rate limit, the API responds with: `429 Too Many Requests`
* A `Retry-After` header will be included in the response, indicating how long the client must wait before retrying the request.
Be sure to implement retry logic and token refresh handling in your integration to avoid disruptions.
# Account Intelligence
Source: https://docs.grailpay.com/docs/risk/bank-accounts/account-intelligence
## Overview
The Account Intelligence v3 endpoint is part of GrailPay's real-time bank account validation system. It allows you to
determine whether a user's bank account is valid, active, and safe to use — helping you reduce fraud, prevent returns,
and accelerate decision-making.
With a single API call, the endpoint returns a `confidence_score` along with detailed `decisioning_insights` based on
the identity and bank account data you provide. These insights can be used to drive onboarding, funding, and payout
workflows with higher accuracy and lower risk.
When constructing your request, you'll pass an `identity` object that contains either an `individual` or an
`organization`. These two are mutually exclusive — the request will fail if both are included. Use `organization` for
validating business entities and `individual` for people.
While the only required fields are account and routing numbers, we strongly recommend providing as much identity data
as possible. More complete input data results in more meaningful confidence scores and more actionable insights.
This API is only available for U.S. bank accounts.
For complete request and response definitions, visit the [Account Intelligence OpenAPI Documentation](https://risk-api.grailpay.com/spec/#/Account%20Validation/post_api_v3_accounts_validate).
***
## Sandbox Testing
The sandbox reserves a small set of routing numbers that return a fixed result. Use them to build and test against each
outcome so you can see how real results will behave. These reserved routing numbers apply across the Risk API, including
both the Account Intelligence and Transaction Intelligence models.
Sandbox base URL: `https://risk-api-sandbox.grailpay.com`
### There Are No Test Account Numbers
Selection is driven by the `routing_number` alone. The account number can be any value that passes standard validation —
numeric, 17 digits or fewer, and not all zeros.
For example, `000000001` paired with any valid account number returns the full-match result. Pairing that same account
number with `000000003` returns the high-risk result.
### Test Routing Numbers
| Routing Number | Scenario | HTTP Status |
| --------------- | ----------------------------------------- | ----------- |
| `000000001` | Low risk, full match | `200` |
| `000000002` | Medium risk, match not available | `200` |
| `000000003` | High risk, negative history | `200` |
| `000000004` | Close taxpayer match | `200` |
| empty / missing | Validation-style errors | `400` |
| `999999999` | Upstream unavailable | `503` |
| Any other value | Falls through to live sandbox decisioning | live |
### What Each Routing Number Returns
For calls to `POST /api/v3/accounts/validate`, each reserved routing number returns the following values:
| Routing Number | `confidence_score` | `name_match` | `name_match_score` | `taxpayer_number_match` |
| -------------- | ------------------ | --------------- | ------------------ | ----------------------- |
| `000000001` | `0.91` | `yes` | `0.98` | `yes` |
| `000000002` | `0.53` | `not_available` | `0.0` | `not_available` |
| `000000003` | `0.12` | `no` | `0.14` | `no` |
| `000000004` | `0.65` | `not_available` | `0.0` | `close_match` |
`confidence_score` is returned at the top level of the response. `name_match`, `name_match_score`, and
`taxpayer_number_match` are returned under `decisioning_insights`.
***
## Actions
By default, every call to this endpoint performs core validation checks on the routing and account numbers. To enrich
the results further, you can optionally include an `actions` object in your request to enable additional validation
layers.
Currently, the following actions are supported:
* `name_match`: Compares the provided name in the identity object with the account holder's name on file.
* `taxpayer_number_match`: Compares the provided taxpayer identifier in the identity object with the account holder's tax ID on file.
To enable either action, include it in the `actions` object of your request payload:
```json theme={"system"}
{
"actions": {
"name_match": true,
"taxpayer_number_match": true
}
}
```
Each enabled action runs an additional identity check that contributes directly to the confidence score and surfaces a
corresponding attribute in the `decisioning_insights` object. Enabling `name_match` returns `name_match` and
`name_match_score`; enabling `taxpayer_number_match` returns `taxpayer_number_match`.
For full request and response examples, see the [Account Intelligence API reference](https://risk-api.grailpay.com/spec/#/Account%20Validation/post_api_v3_accounts_validate).
***
## Response Attributes
Every call to the Account Intelligence endpoint returns a `confidence_score` alongside a set of structured
`decisioning_insights`. Together, these give you both a single risk signal and the explainable attributes behind it,
so you can plug them directly into your own models or rules-based decisioning.
### Confidence Score
The `confidence_score` is a number between `0.00` and `1.00` representing the likelihood that an ACH debit or credit
will be processed successfully without an operational return. Higher is better — a score of `0.95` indicates high
confidence that the account is open, active, and able to transact, while a score of `0.30` indicates significant risk
of an ACH return.
The score is produced by a proprietary machine learning model trained on millions of historical ACH transactions and
their outcomes. The model takes the account and routing number, enriches it with third-party data sources and GrailPay's
own first-party intelligence, and returns a calibrated probability.
As a general guide, the score maps to the following confidence levels:
| Confidence Level | Score Range |
| ---------------- | --------------- |
| Very High | `0.90` – `1.00` |
| Good | `0.70` – `0.89` |
| OK | `0.40` – `0.69` |
| Risky | `0.00` – `0.39` |
### Decisioning Insights
The `decisioning_insights` object contains explainable attributes returned alongside the score. Each attribute is
designed to be consumed directly by your risk engine — as an input to your own models or as a building block for
rules-based logic.
| Attribute | What It Means | Why It Matters | Typical Interpretation | Possible Values |
| ----------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `valid_routing_number` | Confirms the routing number belongs to a legitimate, active financial institution. | An invalid routing number will generally cause the payment to fail. | `true` = valid routing number. `false` = do not process without review. | Boolean, `true` / `false` |
| `name_match` | Compares the provided account owner's name to the name associated with the account. | Helps confirm the person or business owns the account. | `yes` = positive. `no` = name does not appear to match the account owner; review for possible incorrect information or identity concerns. `not_available` = no comparison possible. | String: `yes`, `no`, `not_available`. Returned only when `name_match` is enabled in the request `actions` object. |
| `name_match_score` | Measures how closely the names match on a scale from `0.00` to `1.00`. | Provides additional detail beyond a simple yes/no result. | Near `1.00` = strong match. Around `0.80` = minor differences. Lower scores = weak match. | Number, `0.00` to `1.00`. Returns `0.00` when no name was supplied, or when no identity data source could evaluate the name. |
| `days_since_first_seen` | How long the account has existed in GrailPay's data network. | Older accounts generally provide more confidence. | Higher values = more established. `-1` = no history available. | Integer. `-1` if unknown. |
| `days_since_last_transaction` | Number of days since the account was last active. | Active accounts tend to be lower risk than dormant accounts. | Lower values = recently active. Higher values = less recent activity. | Integer. `-1` if unknown. |
| `positive_transactions_seen` | Indicates successful historical transactions. | Past successful payments increase confidence. | `true` = positive history. | Boolean, `true` / `false` |
| `negative_transactions_seen` | Indicates returned payments or other negative events. | Past issues may increase risk. | `true` = review more closely. | Boolean, `true` / `false` |
| `paid_transactions_seen` | Shows whether previously returned transactions were later resolved. | Shows that past payment issues were actively addressed and resolved, which may reduce the concern associated with previous returns. | `true` = past issues resolved. | Boolean, `true` / `false` |
| `phone_number_present` | Indicates a phone number is associated with the account. | Adds another identity signal. | `true` = slightly stronger identity confidence. | Boolean, `true` / `false` |
| `account_duplicate` | Shows whether the account has been seen before in GrailPay's network. | Prior history provides additional context. | `true` = existing account history. | Boolean, `true` / `false` |
| `taxpayer_number_match` | Compares the provided SSN/EIN with records associated with the account. | Strong identity verification signal. | `yes` = strong match. `close_match` = minor discrepancy. `no` = significant concern. `not_available` = comparison unavailable. | String: `yes`, `no`, `close_match`, `not_available`. Returned only when `taxpayer_number_match` is enabled in the request `actions` object. |
***
## Feedback
Account Intelligence is a continuously improving system, and its accuracy depends on real-world outcome data from your
integration. The Feedback endpoint allows you to report return events that occurred despite receiving an acceptable risk
score, closing the loop between prediction and outcome.
Submitting feedback is a critical part of getting the most out of Account Intelligence. Feedback data is used for
analytics, model evaluation, and ongoing training — directly improving the accuracy of future risk scores across your
account portfolio. Integrations that consistently submit feedback benefit from more precise, tailored risk assessments
over time.
We strongly recommend incorporating feedback submission into your standard return-handling workflow. Consistent
feedback is the single most effective way to improve the accuracy of your Account Intelligence results.
For complete request and response definitions, visit the [Account Intelligence Feedback OpenAPI Documentation](https://risk-api.grailpay.com/spec/#/Account%20Feedback/post_api_v3_accounts_feedback).
### When to Submit Feedback
You should submit feedback when:
* A transaction results in an unexpected return despite receiving a low-risk Account Intelligence score
* You want to improve the accuracy of future risk evaluations for your account portfolio
* You need to report false negatives for internal tracking and model monitoring
### Request Fields
When submitting feedback, your request payload should include details about the account, the return event, and a
reference to the original Account Intelligence evaluation. Required fields ensure we can accurately link your feedback
to the original prediction, while optional fields provide additional context that strengthens model analysis.
#### Required Fields
The following fields are required to link your feedback to the original Account Intelligence evaluation and the
associated return event.
| Field | Type | Description |
| ---------------------- | ------- | ----------------------------------------------------------------------------- |
| `account_number` | string | The account number from the original evaluation |
| `routing_number` | string | The routing number from the original evaluation |
| `outcome_status` | string | The outcome status of the transaction. Possible values: `settled`, `returned` |
| `return_code` | string | The ACH return code received (e.g., `R01`, `R02`, `R03`) |
| `name_match_requested` | boolean | Whether name matching was enabled in the original request |
Additionally, you must include **one** of the following to link to the original prediction:
| Field | Type | Description |
| ---------------------- | --------- | --------------------------------------------------------------------------------------------- |
| `inference_request_id` | UUID | The request ID returned by GrailPay in the original Account Intelligence response (preferred) |
| `inference_timestamp` | timestamp | The UTC timestamp of the original request, if the request ID is unavailable |
Providing the `inference_request_id` is strongly recommended as it ensures accurate linkage to the original risk
evaluation.
#### Optional Fields
The following fields are not required but provide additional context that strengthens model analysis when available.
| Field | Type | Description |
| -------------------------- | --------- | ----------------------------------------------------------- |
| `returned_at` | timestamp | When the return was received |
| `transaction_initiated_at` | timestamp | When the transaction was initiated (client-side) |
| `sec_code` | string | The SEC code of the transaction (e.g., `WEB`, `CCD`, `PPD`) |
| `amount` | decimal | The transaction amount |
| `direction` | string | Transaction direction: `credit` or `debit` |
***
## Usage Recommendations
* Always pass complete and accurate identity data to maximize result quality.
* Use the `confidence_score` and `decisioning_insights` to guide onboarding and risk workflows.
* Review fields such as `name_match`, `valid_routing_number`, and `negative_transactions_seen` closely when the score is low.
* Submit feedback promptly after a return event occurs to ensure accurate timestamps and traceability.
* Always include the `inference_request_id` when available for precise linkage to the original prediction.
* Provide optional fields like `amount`, `sec_code`, and `direction` when possible to enrich the feedback data.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Overview
Source: https://docs.grailpay.com/docs/risk/overview
An overview of our Risk Intelligence API, including its purpose and key features.
Grailpay Risk Intelligence Platform is a modular, API-driven service designed to evaluate risk at critical decision points in the
payment lifecycle. Built to support secure and scalable financial operations, the Risk Intelligence platform provides
real-time analysis for both bank account validation and transaction risk evaluation, helping partners make informed
decisions before onboarding users or moving funds.
Whether you’re evaluating a new payee or assessing the safety of a payment, Risk Intelligence gives you the insights
needed to reduce fraud, enforce compliance, and protect your financial network.
***
#### Account Intelligence
**Account Intelligence** enables you to evaluate the validity of a bank account before funds are moved or
accounts are created. It is intended to support safe onboarding and payment setup by providing insight into the
integrity of account and routing number combinations to prevent operational returns.
#### Transaction Intelligence
**Transaction Intelligence** evaluates the risk associated with a specific transfer of funds between two parties at
that moment in time. It helps determine whether a transaction should be allowed, flagged, or blocked—based on a range
of contextual and behavioral inputs.
***
### Use Cases
* Prevent fraudulent ACH payments before they happen.
* Automate onboarding checks for new users and/or businesses.
* Enforce compliance policies.
* Establish a defensible audit trail for transaction decisions.
***
### Testing & Integration
To get started with the Risk Intelligence API, you can use our sandbox environment to test your integration and
evaluate the API's capabilities without affecting live data. The sandbox mimics the production environment, allowing you to
simulate various scenarios and understand how the API will respond to different inputs. We do have a test account and
routing number available for testing purposes, but if you would like to see a wider range of scenarios, you can use any
valid account and routing number combination to test the API's responses.
Sandbox URL: `https://risk-api-sandbox.grailpay.com`
```json Test Account theme={"system"}
{
"account_number": "11101010",
"routing_number": "053200983"
}
```
### Built for Security & Scale
Risk Intelligence is deployed as a dedicated, high-performance API that integrates seamlessly into your existing
systems. It’s built to scale with your application, whether you’re processing thousands of transactions per day or
vetting high volumes of bank accounts during onboarding.
### Production
When you’re ready to move to production, simply switch your API endpoint to the production URL and use your live
API keys. Our production environment is designed to handle high throughput and low latency, ensuring that you
receive real-time risk assessments without compromising performance. Use the following base URL for production:
`https://risk-api.grailpay.com`
# Guarantees
Source: https://docs.grailpay.com/docs/risk/transactions/guarantees
Guarantees are not enabled by default and require an approved account. If you would like to offer this
functionality, contact your GrailPay integration support contact to have it enabled on your account.
Guarantees are part of GrailPay's risk evaluation system. They build on the same underlying risk signals as
Transaction Intelligence, but rather than returning a score for you to threshold yourself, they return a decision.
***
## Accounts Receivable Guarantees
An accounts receivable guarantee applies to funds being collected by a business. The payee is a verified business
entity—one whose organization details, tax identifier, and beneficial owners have been supplied and evaluated—and the
payer is the party being debited to satisfy what that business is owed. The payer may be an individual or an
organization.
Because the payee is a known and verified business, the risk in a receivable sits almost entirely on the payer side.
This is why the request asks for considerably more about the payer than their bank account alone. Alongside identity
and location, you supply behavioral context: when the payer's account was opened on your platform, how much they have
spent, how many transactions they have completed, whether this is their first, and the device and channel they are
transacting from. GrailPay holds none of this history—it exists only on your platform—and it is what distinguishes an
established customer from one who appeared this morning.
To request a guarantee, send a POST request with details about the payer, the payee, and the transaction. GrailPay
evaluates both parties against its risk signals and returns:
* **Guarantee ID** – A UUID identifying the guarantee record created for this request. Retain this value; it is the
identifier used to cancel the guarantee.
* **Decision** – Either `guarantee` or `no_guarantee`.
Transaction details describe the debit itself: the amount, the SEC code under which it will be originated, the date it
was initiated, and the processing speed.
For full request and response examples, see the [Guarantee API reference](https://risk-api.grailpay.com/spec/#/Guarantee/684a26497ba87a4fd8a162121d596637).
### Usage Recommendations
* Send amounts as integers in cents. A transaction at or above `1000000` cents (\$10,000) is declined automatically and
returns `no_guarantee`.
* Send exactly one of `payer.individual` or `payer.organization`. Name, email, and phone belong to whichever identity
object you send rather than to the payer itself.
* Send all timestamps in UTC. The only date field that is not a timestamp is a beneficial owner's `dob`, which is a
calendar date.
* Use `tax_id` for every tax identifier—individual, organization, and beneficial owner alike.
* Populate the behavioral fields on the payer as completely and accurately as possible. These describe the payer's
history on your platform and are not available to GrailPay from any other source.
* Store the returned guarantee ID against your own record of the transaction so that a cancellation can be issued
without a lookup.
***
## Cancel Guarantees
Not every guarantee that gets issued ends up being wanted. Sometimes the transaction itself falls away—a customer
abandons a checkout, an order is voided, a duplicate submission is caught downstream. Other times the transaction
proceeds exactly as planned and you simply decide you do not need it guaranteed: the payer turns out to be a
long-standing customer, the amount is small enough to absorb, or your own review clears it. Either way, the guarantee
is no longer serving a purpose and should be released rather than left standing.
A guarantee can be cancelled for 15 minutes, measured from the time the original request was received rather than from
when your system read the response. Cancellation is performed against the guarantee ID returned at creation, not
against a transaction or request identifier—which is why that value is worth persisting alongside your own record of
the transaction at the moment you receive it.
For full request and response examples, see the [Cancel Guarantee API reference](https://risk-api.grailpay.com/spec/#/Guarantee/74349eb29e34d3e857b0253973702bfa).
***
## Sandbox Testing
The sandbox reserves a small set of routing numbers that return a fixed decision. Use them to build and test against
each outcome so you can see how real results will behave, without depending on live decisioning to produce a
particular answer.
Sandbox base URL: `https://risk-api-sandbox.grailpay.com`
### Selection Is Driven by the Payer
Scenarios are selected by `payer.bank_account.routing_number` alone. The payee's routing number is not evaluated for
this purpose, and account numbers are not reserved—any value that passes standard validation will do.
Your request is validated in full before a scenario is selected. An invalid payload is rejected on its own terms, so a
reserved routing number will not rescue a request that is missing required fields, sends both `payer.individual` and
`payer.organization`, or violates any other rule.
### Test Routing Numbers
| Payer Routing Number | Scenario | HTTP Status |
| -------------------- | ------------------------------------- | ----------- |
| `000000001` | Approved | `200` |
| `000000002` | Declined | `200` |
| `999999999` | Decisioning temporarily unavailable | `503` |
| Any other value | Falls through to live sandbox scoring | live |
### What Each Routing Number Returns
For calls to `POST /api/v3/guarantees/receivable`, each reserved routing number returns the following:
| Payer Routing Number | `decision` | Guarantee created |
| -------------------- | -------------- | ----------------- |
| `000000001` | `guarantee` | Yes |
| `000000002` | `no_guarantee` | Yes |
| `999999999` | none returned | No |
Both `200` scenarios create a real guarantee record, so the returned ID behaves exactly as it would in production—you
can use `000000001` to test your cancellation flow end to end. The `503` scenario returns a message indicating that
guarantee decisioning is experiencing a disruption and should be retried later; no guarantee is created, so there is
nothing to cancel.
These routing numbers are reserved in the sandbox only. In production they are treated as ordinary routing numbers
and are scored live.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Transaction Intelligence: Accounts Receivable
Source: https://docs.grailpay.com/docs/risk/transactions/transaction-intelligence
Transaction Intelligence is part of GrailPay’s risk evaluation system that helps you assess the safety of a specific
funds transfer between two entities. It is designed to provide real-time insight into whether a transaction should be
allowed, blocked, or escalated for review based on known risk indicators.
To perform a transaction risk evaluation, send a POST request with details about the sender, receiver, and transaction
amount. The system returns a structured response containing:
* **Risk Score** – A floating-point value between `0` and `1` that reflects the probability that the transaction will
succeed without issues. A lower score indicates higher risk; a higher score suggests a safer transaction.
* **Validity Expiration** – A UTC timestamp that indicates how long the evaluation remains valid before it should be
refreshed.
This allows you to implement programmatic decisioning logic in your payment flows, enabling smarter and safer processing
based on risk posture.
For full request and response examples, see the [Transaction Intelligence API reference](https://risk-api.grailpay.com/spec/#/Transaction%20Risk%20Score/post_v1_transactions_score_ar).
***
## Usage Recommendations
* Run Transaction Intelligence on every ACH transfer to detect elevated fraud or return risks in advance.
* Use the `risk_score` as part of your transaction approval logic—e.g., auto-block transactions below a certain threshold
or trigger manual review.
* Cache and store the `validity_expiration` timestamp to avoid unnecessary revalidation for repeated transactions within
a short timeframe.
* For best results, ensure that all identity and transaction metadata is as complete and accurate as possible.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Authentication
Source: https://docs.grailpay.com/docs/technical/authentication
Our Payments API uses Bearer Authentication to enforce authentication for all its incoming requests. Bearer Authentication
(Token Authentication) is an HTTP authentication mechanism that involves security tokens generated by GrailPay servers.
```Bearer Authentication theme={"system"}
{
"Authorization": "Bearer {YOUR_API_KEY}"
}
```
If you have lost an API Key, please connect immediately with our support to block current API Key(s) and generate new one(s).
All API requests must be made over HTTPS. Calls made over HTTP will fail.
### Common Authentication Errors & Warnings
```json 401 (Unauthorized) theme={"system"}
{
"status": false,
"message": "Invalid token.",
"data": null,
"errors": null,
"error_code": {
"type": "client_error",
"subtype": "unauthorized"
}
-----------------------------------------------------
{
"status": false,
"message": "Invalid token format. The token should be in the following format: ID|TOKEN_VALUE. Example: 12345|abcdefg",
"data": null,
"errors": null,
"error_code": {
"type": "client_error",
"subtype": "invalid_token_format"
}
-----------------------------------------------------
{
"status": false,
"message": "Invalid token format. The ID in the token must be an integer. Example: 12345|abcdefg",
"data": null,
"errors": null,
"error_code": {
"type": "client_error",
"subtype": "invalid_token_format"
}
```
# Add Bank Account
Source: https://docs.grailpay.com/docs/technical/bank-accounts/add-bank-account
## Overview
Bank accounts are a core part of ACH transactions in the GrailPay ecosystem. Each transaction involves a **payer**
and **payee**, both of whom must have active, valid bank accounts connected to the application.
If a bank account was not added during initial onboarding (via the registration API), or if you need to attach a new
bank account to an existing user, the **Add Bank Account** API allows you to do so. This endpoint requires the
`user_uuid` of an onboarded **person**, **business**, or **merchant**, which you will include as a path parameter in
your request.
***
## What You'll Need
To add a bank account, you’ll need:
For Manual Account Entry:
* The `user_uuid` of an existing **person**, **business**, or **merchant**
* The bank account details:
* `account_number`
* `routing_number`
* `account_type` (`checking` or `savings`)
* `account_name`
When Using Plaid:
* The `user_uuid` of an existing **person**, **business**, or **merchant**
* A valid `access_token` obtained from the Plaid Link flow
* The `account_id` corresponding to the selected bank account
You also can use the [Bank Link SDK](/docs/technical/bank-link-sdk/overview) to streamline the process of adding
bank accounts. You will need to make sure you are either storing the `user_uuid` from onboarding or retrieving it
from your backend after onboarding to use the SDK to add a bank account to an existing user.
***
## Step 1: Determine the Target User
Before you can add a bank account, you must have successfully onboarded a user and obtained their unique `user_uuid`.
This can be from a **Person**, **Business**, or **Merchant** record.
For more details on onboarding:
* [Onboard a Person](/docs/technical/users/onboard-a-person)
* [Onboard a Business](/docs/technical/users/onboard-a-business)
* [Onboard a Merchant](/docs/technical/users/onboard-a-merchant)
***
## Step 2: Prepare Bank Account Details
You’ll need to collect the following information from the user:
* **Account Number** – A 12-digit bank account number.
* **Routing Number** – A valid 9-digit ABA routing number.
* **Account Type** – Either `checking` or `savings`.
* **Account Name** – A user-facing name or label for this account (e.g., "Main Checking").
***
## Step 3: Enable Account & Routing Validation
Enabling account and routing validation at the time of adding a bank account is optional, but strongly recommended
to help fight fraud and general payment failures.
#### Real-Time Validation (Recommended)
To reduce fraud risk and ensure funds can be successfully routed, we recommend enabling real-time bank account validation
during onboarding. This feature is powered by our [Account Intelligence](/docs/risk/bank-accounts/account-intelligence) product,
which provides intelligent validation of routing and account numbers at the time of submission.
To enable this, include an actions object in your onboarding request:
```json Success (200) theme={"system"}
{
"actions": {
"account_intelligence": {
"version": "v3",
"name_match": true
}
}
}
```
Please note that the Name Match functionality is only available when using V3 of Account Intelligence
This validation step confirms that the provided routing and account numbers are both valid and correctly formatted. If
name\_match is enabled, it also checks that the account holder’s name aligns with the expected identity. In addition to
these checks, the response includes a **Risk Score** and a set of decisioning insights that explain how the account was
evaluated.
To understand the full structure of the response and how to interpret the results, refer to
our [Account Intelligence](/docs/risk/bank-accounts/account-intelligence#response) documentation.
Although optional, enabling this validation is strongly recommended for all production integrations to reduce failure
rates and improve payout reliability.
***
## Step 4: Attribute Billing to a Merchant
Associating a call to add a bank account with a merchant for billing is not required, but it is strongly recommended if your
integration supports it, as this allows your team to better track usage for monitoring and billing purposes.
If your integration supports **merchant-level billing attribution**, you can associate the bank account creation with a
merchant by passing the `billing_merchant_user_uuid` in your request.
### Billing Attribution
**Field:** `billing_merchant_user_uuid`
**Description:** UUID of a merchant-associated user for billing tracking.
This enables detailed cost attribution across your ecosystem and simplifies billing audits.
***
## Step 5: Make the API Call
To add a bank account, make a `POST` request to the following endpoint
```
POST /api/v3/bank-accounts
```
For full schema details, refer to
the [Add Bank Account Endpoint](https://api.grailpay.com/api/documentation#/Bank%20Accounts/7299e7b47155bef6795c0bef1f351f55)
in our OpenAPI docs.
***
## Testing
For guidance on testing in Sandbox, see our [Technical Overview](/docs/technical/overview#testing) guide.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Manage Bank Accounts
Source: https://docs.grailpay.com/docs/technical/bank-accounts/manage-bank-accounts
## Overview
The GrailPay Payments API offers several endpoints to help you manage bank account records linked to people and businesses. These tools allow you to retrieve account details, check balances, set defaults, delete unused accounts, and more.
Each endpoint supports a distinct action and is designed to support common banking workflows such as updates, validations, account transitions, and reconciliation. This guide outlines each management action, what it does, and when to use it.
### Aggregator Types
When working with bank accounts, you may encounter different `aggregator_type` values. These indicate how the bank account was added and can affect available features. There are currently three supported types and they are used throughout many of the bank account management endpoints:
* **plaid**: Indicates the account was added via the GrailPay API using Plaid as the data aggregator.
* **manual**: Indicates the account was added via the GrailPay API using manual entry of account and routing numbers.
* **bank\_link**: Indicates the account was added via the GrailPay Bank Link SDK.
***
## Fetch Account Transaction History
For accounts linked through the GrailPay Bank Link SDK, you can retrieve transaction history directly from the user’s connected bank. This provides visibility into external activity such as deposits, withdrawals, and card payments — useful for risk assessment, reconciliation, and fraud monitoring.
This endpoint returns a list of recent transactions for a specific linked bank account using its `account_uuid` and the aggregator type. It supports date range filtering and pagination to help you tailor the results to your needs.
This functionality is only available for accounts connected through our [Bank Link SDK](/docs/technical/bank-link-sdk/overview).
For full schema details, refer to
* [Fetch Account Transaction History](https://api.grailpay.com/api/documentation#/Bank%20Accounts/c16357b01c82188955fbb968e9e7004e)
***
## Fetch Bank Account Owners
For accounts linked through the GrailPay Bank Link SDK, you can retrieve owner information directly from the user's
connected bank. This provides access to verified account holder details such as name, address, and contact information
— useful for identity verification, compliance checks, and onboarding workflows.
This endpoint returns the owner information associated with a specific linked bank account using its `account_uuid`
and the aggregator type.
This functionality is only available for accounts connected through our [Bank Link SDK](/docs/technical/bank-link-sdk/overview).
For full schema details, refer to
* [Fetch Bank Account Owners](https://api.grailpay.com/api/documentation#/Bank%20Accounts/d4e32681a9d14d638f4b817a6a28d555)
## Delete Bank Account
You can delete a bank account associated with a person or business by referencing the `aggregator_type` used and the `account_uuid`. This is typically used to remove old or unused bank accounts that should no longer be involved in transactions.
Deletion is **permanent** and should be used only after ensuring the account is no longer in use.
Bank accounts can only be deleted after a period of time has passed since the last transcation was settled, to account for potential returns eliciting clawbacks on the bank account. This time period is 30 days for business bank accounts, and 70 days for consumer bank accounts.
For full schema details, refer to
* [Delete Bank Account](https://api.grailpay.com/api/documentation#/Bank%20Accounts/8dd66a43ca30cc96fc05dd01d1d9e632)
***
## Fetch Bank Account
This endpoint allows you to retrieve detailed information about a specific bank account using the `account_uuid`. It's helpful for auditing, troubleshooting, or displaying bank account metadata within your application.
For full schema details, refer to
* [Fetch Bank Account](https://api.grailpay.com/api/documentation#/Bank%20Accounts/155c0871e6d5b16259c5e154f7b2862b)
***
## Fetch Account Balance
Use this endpoint to retrieve the current balance of a linked bank account. This is especially useful for validating available funds prior to initiating a transaction or for displaying account balances in your product UI.
Keep in mind that this balance check is only available to accounts connected through our Bank Link Widget. If attempted on an account added through the API ( manual\_account ), we will respond with an error with a status code of 403.
For full schema details, refer to
* [Fetch Account Balance](https://api.grailpay.com/api/documentation#/Bank%20Accounts/8c3f98d9b87452d7e6719d1f2c6af704)
***
## List All Bank Accounts
This endpoint returns a list of all bank accounts associated with a specific user, whether that user is a person or business. It supports pagination and is useful for building dashboards or reconciling linked accounts.
For full schema details, refer to
* [List Bank Accounts](https://api.grailpay.com/api/documentation#/Bank%20Accounts/ec0c661bef93387bceecdbf34ee6cd06)
***
## Set Default Account
When users have multiple linked bank accounts, this endpoint allows you to mark one of them as the default for future transactions. This is useful for setting preferred disbursement or funding accounts for your users.
For full schema details, refer to
* [Set Default Account](https://api.grailpay.com/api/documentation#/Bank%20Accounts/c6a670c7331dc25c3def2a0c98f0c973)
***
## Testing
For guidance on testing in Sandbox, see our [Technical Overview](/docs/technical/overview#testing) guide.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Validate Bank Accounts
Source: https://docs.grailpay.com/docs/technical/bank-accounts/validate-bank-accounts
## Overview
Before initiating a transaction or linking a manually entered bank account, it's important to ensure the account and
routing number combination is valid. GrailPay offers a dedicated endpoint that validates these details in real-time to
reduce the risk of failed transactions and ACH returns.
This validation is especially useful during onboarding or when updating a user's financial information, as it helps
ensure the account is active and properly formatted for ACH.
This endpoint only supports manually entered bank accounts. For accounts linked through Bank Link or Plaid,
validation is handled as part of the linking process.
***
## Use Cases
You might use this endpoint when:
* Collecting account details via a custom onboarding form
* Adding a bank account outside the registration flow
* Verifying bank details before submitting a transaction
* Implementing safeguards to reduce return-related fraud
* Performing name matching verification against account holder information
***
## Account Intelligence Versions
The response payload varies depending on which version of account intelligence you select. We recommend using **V3** for the most comprehensive validation insights.
**V1 Deprecation Notice:** Version 1 account intelligence is deprecated and will be removed in a future release.
Please migrate to V3 for enhanced decisioning insights.
### Version 3 (Recommended)
V3 provides comprehensive decisioning insights including transaction history, name matching, and duplicate detection.
For complete request and response schema, refer to:
* [Validate Account & Routing V3](https://api.grailpay.com/api/documentation#/Bank%20Accounts/77650b67f45408f8ec5d610ec96239d2)
To enable name matching, set `name_match: true` in the `actions.account_intelligence` object of your request payload.
When name matching is enabled, the `person` object with `first_name` and `last_name` is required.
If you are not using name matching, the `person` object is optional.
#### V3 Response Fields
To learn more about our V3 Response fields, refer to the [Account Intelligence V3 Documentation](/docs/risk/bank-accounts/account-intelligence).
***
### Version 2
V2 returns a confidence score with a human-readable confidence level.
```json theme={"system"}
{
"account_intelligence": {
"confidence_score": 0.81,
"confidence_level": "very high",
"version": "v2"
}
}
```
#### V2 Response Fields
| Field | Type | Description |
| ------------------ | ------- | ---------------------------------------------------------------------------- |
| `confidence_score` | decimal | Overall confidence score (0.0 - 1.0) |
| `confidence_level` | string | Human-readable confidence level (e.g., `very high`, `high`, `medium`, `low`) |
***
### Version 1 (Deprecated)
V1 is deprecated. Please migrate to V3.
V1 returns a simple validity status.
```json theme={"system"}
{
"account_intelligence": {
"validity": "valid",
"version": "v1"
}
}
```
#### V1 Response Fields
| Field | Type | Description |
| ---------- | ------ | --------------------------------------------------------- |
| `validity` | string | Validation result: `valid`, `invalid`, or `not_validated` |
***
## Testing
You can test the validation endpoint in our [Sandbox Environment](/docs/technical/sandbox). The sandbox reserves a set
of routing numbers that each return a fixed validation outcome, letting you build against every result.
When using **V3** as the Account Intelligence version, the sandbox test routing numbers and the exact values each one
returns are documented in full on the [Account Intelligence](/docs/risk/bank-accounts/account-intelligence#sandbox-testing)
page. Refer there for the complete V3 testing reference.
A quick summary of the primary routing numbers:
```json Low Risk ( valid account with name match ) theme={"system"}
{
"account_number": "1234567890",
"routing_number": "000000001"
}
```
```json Medium Risk ( name match not available ) theme={"system"}
{
"account_number": "1234567890",
"routing_number": "000000002"
}
```
```json High Risk ( negative history ) theme={"system"}
{
"account_number": "1234567890",
"routing_number": "000000003"
}
```
```json Simulates 503 error (service unavailable) theme={"system"}
{
"account_number": "1234567890",
"routing_number": "999999999"
}
```
### Version 2 Testing
When using `v2` as the Account Intelligence version in the `actions` object of your request payload, the same routing
numbers can be used to simulate different validation outcomes. Version 2 responses include a confidence score and
confidence level, but do not include the detailed insights provided in Version 3.
* `000000001` — High confidence score (e.g., 0.81) with a confidence level of `very_high`
* `000000002` — Medium confidence score (e.g., 0.65) with a confidence level of `medium`
* `000000003` — Low confidence score (e.g., 0.30) with a confidence level of `low`
### Version 1 Testing
When using `v1` as the Account Intelligence version in the `actions` object of your request payload, the same routing
numbers can be used to simulate different validation outcomes. Version 1 responses only indicate whether the account
is `valid`, `invalid`, or `not_validated` — no confidence score or level is provided.
* `000000001` — Returns `valid`
* `000000002` — Returns `invalid`
* `000000003` — Returns `not_validated`
***
## Next Steps
After successful validation, you can proceed to [Add the Bank Account](/docs/technical/bank-accounts/add-bank-account)
to the user profile, or use the validated details to initiate a transaction.
For complete request and response schema, refer to:
* [Validate Account & Routing V3](https://api.grailpay.com/api/documentation#/Bank%20Accounts/77650b67f45408f8ec5d610ec96239d2)
***
# Bank Link SDK v3.0
Source: https://docs.grailpay.com/docs/technical/bank-link-sdk/overview
## Overview
This guide explains the Bank Link SDK workflow and provides step-by-step instructions for integrating the GrailPay Bank
Link SDK into a web application and successfully completing the bank-linking process.
If you are currently using Version 1 or Version 2 of the Bank Link SDK, refer to our
[Upgrade Guide](/docs/technical/bank-link-sdk/upgrade-guide) for a detailed walkthrough of the changes required to
migrate to V3.
### Bank Link SDK URLs
* **Production** — `https://banklink.grailpay.com/grailpay-banklink.js`
* **Sandbox** — `https://banklink-sandbox.grailpay.com/grailpay-banklink.js`
## Requirements
Before integrating the Bank Link SDK, ensure you have the following:
* A Bank Link API Key
* The Bank Link SDK Script URL for your target environment
## Environment Configuration
GrailPay provides separate SDK script URLs and API keys for each environment.
* **Sandbox** — Use the sandbox SDK script URL along with your sandbox Bank Link API key.
* **Production** — Use the production SDK script URL along with your production Bank Link API key.
Do not use sandbox credentials in production or vice versa. Each environment requires its own API key and SDK script URL.
***
## Integration
### Add the SDK Script
Include the following script tag in your application, replacing `` with the SDK URL for your target environment:
```html theme={"system"}
```
### Initialize the SDK
Call `GrailPay.BankLink.init()` to initialize and launch the SDK. The only required parameter is `token` — all other
parameters are optional and can be added as needed for your integration.
```javascript theme={"system"}
document.addEventListener('DOMContentLoaded', function () {
GrailPay.BankLink.init({
token: "YOUR_BANK_LINK_TOKEN"
})
.then(() => {
console.log("SDK initialized successfully");
});
});
```
`init()` both initializes and opens the SDK in a single call. There is no separate `open()` method.
***
## Configuration
### Entity Management
If you are linking a bank account to an entity that has already been onboarded with GrailPay, pass the entity's UUID
during initialization. If omitted, the SDK will create a new entity and return its UUID via the `onEntityCreated` callback.
```javascript theme={"system"}
GrailPay.BankLink.init({
token: "YOUR_BANK_LINK_TOKEN",
entity_uuid: "EXISTING_ENTITY_UUID",
entity_type: "person",
client_reference_id: "YOUR_REF_ID"
});
```
| Parameter | Description |
| --------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `entity_uuid` | UUID of an existing Person or Business already onboarded with GrailPay. Omit or set to `null` to create a new entity. |
| `entity_type` | Specifies the entity type to create. Accepted values: `person`, `business`. |
| `client_reference_id` | A client-defined identifier associated with the created entity and connected bank account. |
### Branding
You can customize the SDK's appearance to match your application by providing branding configuration during initialization.
```javascript theme={"system"}
GrailPay.BankLink.init({
token: "YOUR_BANK_LINK_TOKEN",
branding: {
company_name: "XYZ, LLC",
logo: "https://yourapp.com/logo.png",
primary_color: "#ff3902"
}
});
```
| Parameter | Description |
| ------------------------ | ---------------------------------------------------------------- |
| `branding.company_name` | Display name shown as the SDK title. |
| `branding.logo` | Public URL of your brand logo displayed in the SDK. |
| `branding.primary_color` | Hex color value used to theme the SDK to match your application. |
### Billing
If billing should be attributed to a specific Merchant, pass the Merchant's UUID or processor MID during initialization.
```javascript theme={"system"}
GrailPay.BankLink.init({
token: "YOUR_BANK_LINK_TOKEN",
billing_merchant_uuid: "EXISTING_MERCHANT_UUID",
billing_processor_mid: "EXISTING_PROCESSOR_MID"
});
```
| Parameter | Description |
| ----------------------- | ----------------------------------------------------------------- |
| `billing_merchant_uuid` | UUID of a specific Merchant to whom billing should be attributed. |
| `billing_processor_mid` | MID of a specific Merchant to whom billing should be attributed. |
***
## Callbacks
Register callback functions during initialization to respond to events throughout the Bank Link flow.
```javascript theme={"system"}
GrailPay.BankLink.init({
token: "YOUR_BANK_LINK_TOKEN",
onEntityCreated: function (data) {
console.log("Entity created", data);
},
onBankConnected: function (data) {
console.log("Bank connected", data);
},
onLinkedDefaultAccount: function (data) {
console.log("Default account linked", data);
},
onLinkExit: function (data) {
console.log("User exited", data);
},
onError: function (error) {
console.error("Error occurred", error);
}
});
```
### onEntityCreated Callback
Triggered when a new entity (Person or Business) is successfully created.
```javascript theme={"system"}
{
"entity_uuid": "a198d598-8a87-47b0-95db-102f12ce8b2d",
"entity_user_uuid": "a198d598-68b5-42b3-9367-521a105f8eb6",
"entity_type": "business",
"created_at": "2026-04-21 13:25:06"
}
```
### onBankConnected Callback
Triggered after successful bank account connections. Returns all linked accounts with their respective statuses.
```javascript theme={"system"}
{
"entity_uuid": "a198d598-8a87-47b0-95db-102f12ce8b2d",
"entity_user_uuid": "a198d598-68b5-42b3-9367-521a105f8eb6",
"bank_accounts": [
{
"account_uuid": "019db039-4aa4-73c9-bd48-d9e8489dbc5b",
"account_number_last4": "1111",
"routing_number_last4": "1533",
"account_name": "Plaid Silver Standard 0.1% Interest Saving",
"account_type": "savings",
"institution_name": "Chase",
"aggregator": "PLAID",
"status": "connected",
"created_at": "2026-04-21 13:27:17",
"updated_at": "2026-04-21 13:27:18"
}
]
}
```
### onLinkedDefaultAccount Callback
Triggered when a default bank account is selected. Includes account details and status (`pending` or `connected`).
```javascript theme={"system"}
{
"entity_uuid": "a198d598-8a87-47b0-95db-102f12ce8b2d",
"entity_user_uuid": "a198d598-68b5-42b3-9367-521a105f8eb6",
"bank_account": {
"account_uuid": "019db039-4aa4-73c9-bd48-d9e8489dbc5b",
"account_number_last4": "1111",
"routing_number_last4": "1533",
"account_name": "Plaid Silver Standard 0.1% Interest Saving",
"account_type": "savings",
"institution_name": "Chase",
"aggregator": "PLAID",
"status": "connected",
"created_at": "2026-04-21 13:27:17",
"updated_at": "2026-04-21 13:27:18"
}
}
```
### onLinkExit Callback
Triggered when the user cancels or exits the bank link process.
```javascript theme={"system"}
{
"entity_uuid": "a198d598-8a87-47b0-95db-102f12ce8b2d",
"entity_user_uuid": "a198d598-68b5-42b3-9367-521a105f8eb6",
"status": "COMPLETE",
"exited_at": "2026-04-21 13:27:27"
}
```
### onError Callback
Triggered when an error occurs during the flow, including integration issues, authorization failures, or widget launch errors.
```javascript theme={"system"}
{
"error_message": "Invalid token.",
"entity_uuid": null,
"entity_user_uuid": null,
"failed_at": "2026-04-21 13:32:08"
}
```
***
## Tokenized Account Numbers
Some financial institutions issue a Tokenized Account Number (TAN) in place of the account holder's real account number
when an account is connected through open banking. A TAN is a securely generated stand-in for the real account number —
it is paired with a routing number, can be used for ACH transactions exactly like a standard account number, and is
resolved back to the underlying account by the issuing bank at settlement.
Tokenization limits the exposure of real account credentials and allows the account holder or their financial institution
to revoke a specific application's access to the account without disrupting the account itself.
Chase is the institution most commonly returning TANs today. Other large institutions are expected to adopt the same
approach, so your integration should not assume this behavior is limited to a single bank.
### What This Means for Your Integration
When an account is first connected, GrailPay receives a masked identifier representing the last four digits of the
customer's **real** account number. Later, when full account details are retrieved for payment processing, the
institution may return a TAN rather than the real account number.
As a result, the account number GrailPay stores and uses for ACH processing may not match the last four digits your
customer saw during the linking flow. This is expected behavior and does not indicate that a different account was
connected.
To keep the customer-facing identifier consistent, GrailPay always sets `account_number_last4` from the masked value
captured at connection time, and never overwrites it when full account details are retrieved later. The `account_number_last4`
returned in the `onBankConnected` and `onLinkedDefaultAccount` callbacks will always reflect the customer's real account
number, not the TAN.
If you display bank account identifiers in your own application, use `account_number_last4`. This is the value your
customer will recognize from their bank.
A TAN is tied to the connection the customer authorized. If the customer revokes that connection through their
financial institution, the TAN is invalidated and subsequent ACH transactions against it will fail. In that scenario,
the customer must reconnect their account through the Bank Link SDK.
***
## Closing the SDK
To programmatically close the SDK at any time:
```javascript theme={"system"}
GrailPay.BankLink.close();
```
***
## Bank Link Flow
Once initialized, the Bank Link SDK guides the user through the following steps:
### Finder Screen
The initial screen where users select their bank from the available list.
### OAuth Screen
Users authenticate securely using their bank credentials via the bank's OAuth flow.
### Processing Screen
The system processes the selected accounts and connects them to GrailPay.
### Default Account Selection Screen
After a successful connection, the user selects a default account for payments.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Bank Link SDK Upgrade Guide
Source: https://docs.grailpay.com/docs/technical/bank-link-sdk/upgrade-guide
## Overview
The Bank Link SDK V3 introduces a modern, modular architecture with improved entity management, enhanced branding
controls, and a simplified API surface. This guide walks you through the changes required to upgrade your existing
integration to V3.
### Key Improvements
* Modular API (`GrailPay.BankLink`)
* Built-in entity lifecycle management
* Simplified configuration (removed redundant parameters)
* Improved callback structure
* ES Module + UMD support
* OAuth redirect handling support
### Bank Link SDK URLs
* **Production** — `https://banklink.grailpay.com/grailpay-banklink.js`
* **Sandbox** — `https://banklink-sandbox.grailpay.com/grailpay-banklink.js`
***
## Breaking Changes
The following changes in V3 are not backward compatible with previous versions. Review these carefully before beginning
your migration.
* `window.grailpay` has been replaced with `GrailPay.BankLink` — all method calls must be updated
* `init()` now automatically opens the widget — the separate `open()` method has been removed
* `userId` has been replaced with `entity_uuid`, which represents an existing Person or Business entity
* `role` has been renamed to `entity_type` (same accepted values: `person`, `business`)
* `vendorId` is no longer required and has been removed
* `timeout` is no longer required and has been removed
* The `theme` object has been replaced with a new `branding` object — UI text customization via `theme.screens` is no longer supported
* The `onUserCreated` callback has been renamed to `onEntityCreated`
* Response payloads for `onBankConnected`, `onLinkExit`, and `onError` have changed — see [Callback Changes](#callback-changes) below
***
## Initialization
In previous versions, the SDK was initialized via `window.grailpay.init()` and required a separate `open()` call to
display the widget. In V3, `GrailPay.BankLink.init()` handles both initialization and opening the widget in a single
call.
**Previous**
```javascript theme={"system"}
window.grailpay.init({
token: "TOKEN",
userId: "USER_UUID",
vendorId: "VENDOR_ID",
role: "business",
timeout: 10,
theme: {
branding_name: "Your Company",
screens: {
finder: {
subtitle: "Connect your bank account",
searchPlaceholder: "Search for your bank..."
}
}
},
onUserCreated: function (data) { /* ... */ },
onBankConnected: function (data) { /* ... */ }
});
// Separate call required to open the widget
window.grailpay.open();
```
**V3**
```javascript theme={"system"}
GrailPay.BankLink.init({
token: "TOKEN",
entity_uuid: "EXISTING_ENTITY_UUID", // optional — omit to create a new entity
entity_type: "business", // "person" or "business"
client_reference_id: "YOUR_REF_ID", // optional — your internal reference ID
branding: {
company_name: "Your Company",
primary_color: "#4F46E5",
logo: "https://yourapp.com/logo.png"
},
onEntityCreated: function (data) { /* ... */ },
onBankConnected: function (data) { /* ... */ },
onLinkedDefaultAccount: function (data) { /* ... */ },
onLinkExit: function (data) { /* ... */ },
onError: function (error) { /* ... */ }
});
// No separate open() call needed — init() opens the widget automatically
```
***
## Parameter Mapping
Use the table below to map your existing parameters to their V3 equivalents.
| Previous Parameter | V3 Parameter | Notes |
| ------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `token` | `token` | No change. |
| `userId` | `entity_uuid` | Pass the UUID of an existing Person or Business. If omitted, the widget will create a new entity and return its UUID via `onEntityCreated`. |
| `role` | `entity_type` | Renamed. Same accepted values: `person`, `business`. |
| `vendorId` | Removed | No longer required. Vendor context is derived from the API token. |
| `timeout` | Removed | No longer required. |
| `theme.branding_name` | `branding.company_name` | Moved into the new `branding` object. |
| `theme.screens.*` | Removed | UI text customization is no longer supported. |
| N/A | `branding.primary_color` | New. Set a custom primary color for the widget. |
| N/A | `branding.logo` | New. Display your company logo in the widget. |
| N/A | `client_reference_id` | New. Pass your own internal reference ID for tracking purposes. |
| `billingMerchantUserUuid` | `billing_merchant_uuid` | Renamed. |
| `billingProcessorMid` | `billing_processor_mid` | Renamed. |
***
## Branding Changes
The `theme` object has been replaced with a simplified `branding` object. UI text customization via `theme.screens` is
no longer supported.
**Previous**
```javascript theme={"system"}
theme: {
branding_name: "Company",
screens: {
finder: {
subtitle: "...",
searchPlaceholder: "..."
}
}
}
```
**V3**
```javascript theme={"system"}
branding: {
company_name: "Company",
primary_color: "#4F46E5",
logo: "https://yourapp.com/logo.png"
}
```
***
## Callback Changes
| Previous Callback | V3 Callback | What Changed |
| ------------------------ | ------------------------ | -------------------------------------------------------------- |
| `onUserCreated` | `onEntityCreated` | Renamed to reflect the entity model. Response payload updated. |
| `onBankConnected` | `onBankConnected` | Response payload updated. |
| `onLinkedDefaultAccount` | `onLinkedDefaultAccount` | Response payload updated. |
| `onLinkExit` | `onLinkExit` | Response payload simplified. |
| `onError` | `onError` | New error structure. |
***
## Closing the Widget
The method to programmatically close the widget has been updated to use the new module path.
**Previous**
```javascript theme={"system"}
window.grailpay.close();
```
**V3**
```javascript theme={"system"}
GrailPay.BankLink.close();
```
***
## Entity vs. User Model
Previous versions used a "user" model where users could be implicitly created during the Bank Link flow. V3 introduces
an explicit "entity" model that gives you more control over lifecycle management.
| Behavior | Previous | V3 |
| ----------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Creating a new entity | Implicit — a user was created automatically if `userId` was not provided. | Explicit — omit `entity_uuid` to create a new entity. The UUID is returned via `onEntityCreated`. |
| Connecting an existing entity | Pass `userId`. | Pass `entity_uuid` with the UUID of an existing Person or Business. |
| Tracking with your own IDs | Not supported. | Use `client_reference_id` to associate your internal reference ID. |
***
## Migration Checklist
This checklist covers the full set of changes required to complete your migration. We recommend working through
these items in order and testing the full Bank Link flow end-to-end in Sandbox before deploying to production.
* Replace the SDK script tag with the V3 script tag
* Update all SDK method calls from `window.grailpay` to `GrailPay.BankLink`
* Update initialization to use `GrailPay.BankLink.init()` with the new configuration structure
* Rename `userId` to `entity_uuid`
* Rename `role` to `entity_type`
* Remove deprecated parameters: `vendorId`, `timeout`
* Replace the `theme` object with the new `branding` object
* Rename the `onUserCreated` callback to `onEntityCreated`
* Update all callback handlers to handle the new response payloads
* Remove any calls to `open()` — `init()` now opens the widget automatically
* Update `close()` calls to use `GrailPay.BankLink.close()`
* Test the full Bank Link flow end-to-end in Sandbox
***
## Questions?
If you encounter any issues during your upgrade, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Get Merchant Billing Summary
Source: https://docs.grailpay.com/docs/technical/billing/get-merchant-billing-summary
## Overview
The Get Merchant Billing Summary endpoint allows you to retrieve a list of billing events associated with a specific
business (also referred to as a merchant). This is particularly useful for vendors or partners who need to track usage,
audit charges, or generate reports for invoicing and financial reconciliation.
Each billing item returned corresponds to a distinct event that triggered a charge—such as a transaction, payout, refund,
or bank account validation. These events represent usage of the GrailPay platform and are tied to specific API activity
for the given merchant.
You can filter results by date range to isolate events within a specific billing cycle or time window. The endpoint is
also paginated to efficiently return large data sets for merchants with high transaction volume.
For complete request and response schema details, refer to the [OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Billing/6fdcd5190b6f97e4184ab8e1c4ae9464)
***
## Use Cases
Below are a couple of common scenarios where you might use this endpoint.
### Scenario 1 - Get All Billing Events
Let’s assume you have onboarded a new merchant, Acme Tool Company, using the Onboard a Merchant API. As part of the
onboarding process, you received a UUID of abcd-efgh-ijkl-mnop.
Since onboarding, you’ve created 20 transactions for this merchant: 10 in January and 10 in February. Each transaction
is a Standard ACH with a billing rate of **\$0.10** per event.
To retrieve all billing events for this merchant, simply make a GET request to:
`/3p/api/v2/merchants/abcd-efgh-ijkl-mnop/billing`
The response will include a summary array containing a single object with:
* billable\_event: Standard ACH
* total\_occurrences: 20
* amount\_per\_occurrence: 10 (cents)
* total: 200 (cents = \$2.00)
### Scenario 2 - Get Billing Events for Date Range
Using the same merchant and transaction data, suppose you only want to retrieve billing events for January.
You can pass a date range in the query parameters:
`/3p/api/v2/merchants/abcd-efgh-ijkl-mnop/billing?start_date=2025-01-01&end_date=2025-01-31`
The response will include a summary array containing a single object with:
* billable\_event: Standard ACH
* total\_occurrences: 10
* amount\_per\_occurrence: 10 (cents)
* total: 100 (cents = \$1.00)
***
## Testing
For guidance on testing in Sandbox, see our [Technical Overview](/docs/technical/overview#testing) guide.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Idempotency
Source: https://docs.grailpay.com/docs/technical/idempotency
Our Payments APIs support idempotency, enabling you to safely retry requests without accidentally duplicating operations.
To make an idempotent request, include an additional `Idempotency-Key` header. This key prevents the creation of duplicate objects or repeated updates if a connection error occurs, allowing you to safely retry the request.
An idempotency key is a unique value generated by the client and recognized by the server to identify retries of the same request. We recommend using V4 UUIDs or similar random strings to minimize the risk of collisions. The maximum length for an idempotency key is 255 characters.
The idempotency mechanism saves the response status code and body of the initial request associated with a specific idempotency key. Any subsequent requests to the same endpoint with the same idempotency key will yield the identical response for at least 24 hours. After this period, the key is purged from the system, and using the same key will result in a new request.
Only POST and PUT request endpoints support idempotency keys. Using these keys with other types of requests is ineffective and should be avoided.
For more information, please see the example below, which illustrates how to submit the `Idempotency-Key` header in an API request:
```curl curl theme={"system"}
curl --location 'https://api-sandbox.grailpay.com/3p/api/v1/register/person' \
--header 'Accept: application/json' \
--header 'Idempotency-Key: afc24e55-8321-4172-8903-67e7ed80f07b' \
--header 'Content-Type: application/json' \
--header 'Authorization: ••••••' \
--data '{
"first_name": "Will",
"last_name": "Clance",
"address": {
"line_1": "681 Avenue C",
"city": "Bayonne",
"state": "NJ",
"zip": "07002"
}
}'
```
# Payments Platform Overview
Source: https://docs.grailpay.com/docs/technical/overview
This content is designed as a launching pad to integration, and provides key details relevant to processing ACH with GrailPay and using our Risk Intelligence tools. This context should be helpful to developers and implementation teams before jumping into our technical integration guides and OpenAPI documentation.
## Introduction
Welcome to our Payments Platform Guide, in which we detail how our system interacts with the ACH payment rail, as well as
key considerations around payment behaviors, platform actions, and operational considerations that you should be aware
of as you dive into your integration.
ACH (Automated Clearing House) is an electronic bank payment system that enables consumers and businesses in the United
States to make payments between bank accounts. ACH transactions are processed several times in batch windows set by the
Federal Reserve and according to regulations set by the National Automated Clearing House Association (NACHA).
## What's Included
1. Key Definitions
2. User Onboarding
3. Payment Behavior
4. Payment Speeds
5. Additional Payment Scenarios
6. Operations
7. Testing
***
## Key Definitions
Start here to understand terms important to an ACH transaction.
### Entities
**Federal Reserve**
Serves as the backbone of the ACH network, acting as the central clearing facility for most ACH transactions. The Fed
dictates the processing windows in which transfers are processed, sets the bank holiday schedule that ACH payments abide
by, and manages regulations that determine underlying processing requirements and best practices.
**National Automated Clearing House Association (NACHA)**
The governing body of ACH, NACHA is the regulatory entity managing the administration, development, and governance of
the network. It works closely with the Federal Reserve.
**Originating Depository Financial Institution (ODFI)**
The financial institution that initiates ACH entries at the request of its clients. ODFIs assume the risk and regulatory
responsibility for ACH entries they originate and may set category restrictions, transaction limits, monitoring
thresholds, and reserve requirements on clients, in addition to maintaining certain standards for visibility into
participating businesses and their transaction data.
**Payee**
The business or individual receiving an ACH payment.
**Payer**
The consumer or business authorizing a payment and sending funds for an ACH payment.
**Processor**
Organizations that handle the technical aspects of routing ACH files. GrailPay is an ACH processor.
**Receiving Depository Financial Institution (RDFI)**
The financial institution that receives ACH payments and posts them to the accounts of its clients. RDFIs may have
their own return rate thresholds and monitoring practices.
For a larger list of ACH-relevant definitions, visit our [Key Terms page in the Resources](/docs/resources/key-terms)
section.
### ACH Entry Types
GrailPay offers three ACH entry types: WEB, CCD, and PPD. These different types of ACH have different rulesets applied
by regulators, and require different handling.
**WEB**
Used to debit funds from a **consumer** bank account and requires explicit documented authorization. It is commonly
used for online bill payments, subscription services, and e-commerce purchases.
**CCD**
Used for the transfer of funds between business accounts (B2B payments). It is often used for vendor invoice payments,
corporate disbursements, and rent and utility payments.
**PPD**
Used for consumer push payments, commonly for direct deposit of payroll.
**Summary of ACH Entry Types**
| Consideration | WEB | CCD | PPD |
| ------------------------------------ | --------------------------------------------------------------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------- |
| Fraud risk | High | Medium | Medium |
| Applicable return timeframes | Most returns come in 2 banking days, with some returns having a 60 day allowable window | 2 banking days, some rare exceptions | Most returns come in 2 banking days, with some returns having a 60 day allowable window |
| Bank account validation requirements | Required | Recommended | Recommended |
***
## User Onboarding
GrailPay supports three user types for participation in payments on our platform: Merchants, Businesses, and People. Each
has a dedicated API endpoint for onboarding, and requires slightly different treatment for successful onboarding and
management.
**Merchants**
These are companies that undergo our KYB verification, requiring specific information on the underlying business and its
owners. GrailPay requires that at least one side of every payment be KYB'd, meaning that a merchant can be thought of as
the anchor of an ACH payment on our platform.
**KYB (Know Your Business)**
This is the process of identifying and verifying the identity of a corporate customer to assess risk and ensure
compliance with applicable regulations. This involves collecting and verifying documents related to a business's
legal structure and ownership, as well as checking for adverse media, sanctions, or high-risk affiliations. The goal
is to prevent financial crimes, by ensuring we do not process payments for illegitimate or high-risk entities.
**Businesses**
These are companies that do not undergo our KYB verification. Onboarding a new company to GrailPay as a business rather
than a merchant facilitates B2B payments without requiring KYB on both entities. This payment flow creates a CCD ACH.
**People**
These are individuals who are added to participate in ACH payments to businesses. This payment flow creates WEB ACHs.
**Bank account validation**
GrailPay supports verification of bank accounts at the time you onboard users. This is a critical step in reducing the
risk of ACH payment failures due to preventable operational causes like typos during entry or outdated routing
numbers. Our [Account Intelligence](/docs/risk/bank-accounts/account-intelligence) offering, a foundational tool within
our Risk Intelligence suite, provides a risk score assessing the validity of a bank account and its ability to
successfully transact ACH payments.
***
## Payment Behavior
ACH follows consistent behavioral pathways from payer to payee. GrailPay defines the movement of funds with an ACH
payment as a Transaction.
Every Transaction has two legs, or, in ACH terms, originations. The first leg is the Transaction Capture, sometimes
known as the debit, or pay-in, and the second leg is the payout, or credit.
**Payment = Transaction Capture + Payout**
* Transaction Capture = first origination or debit
* Payout = second origination or credit
### Transaction Capture
This is the initiation of the funds' movement and tracks the related information regarding the debit side of the
transfer, taking the funds from the payer. GrailPay's webhooks provide numerous real-time status updates on the
debit as it experiences event updates through the processing lifecycle.
**Transaction Started**
This event is fired when the Transaction is created within our system, but the ACH has not begun to process yet. This
should occur in a short time following the call to our API to create the transfer of funds.
**Transaction Capture Started**
This event is fired when an ACH debit has been successfully initiated on the related transaction. It indicates the funds
capture process has begun from the payer's bank account.
**Transaction Failed**
This event is fired if the transaction fails during any stage of processing, whether due to account issues, validation
errors, or ACH Return Codes. Most of these will be due to ACH Return Codes as most of our validation checks occur
prior to the transaction being created.
**Transaction Completed**
This event is fired when the debit side of the transfer has completed. This means that the debit from the payer has
successfully settled and we are initiating the transfer of those funds to the payee.
**Transaction Canceled**
This event is fired when a transaction is canceled and confirms that no funds will be captured or disbursed. There
are limitations on the allowable time frame to cancel a transaction. Once we have processed a debit, indicating that the
bank has received and accepted the ACH entry, a cancellation will not be accepted by the system.
**Transaction monitoring**
GrailPay supports active monitoring of individual transactions with [Transaction Intelligence](/docs/risk/transactions/transaction-intelligence).
This functionality allows you to assess the likelihood that a specific ACH transaction from a payer will successfully
settle, and uses forensic analysis to predict a transaction's risk at that moment in time.
### Payout
Once the debit of the ACH has completed, the payout is created, representing the credit side of the transfer, which is
us moving the funds to the payee.
When processing ACH payments, the participating financial institutions ultimately dictate the exact timing at which
funds are moved from and into accounts. This means that while an ACH entry may have been processed successfully by
GrailPay and indicate settlement (meaning the bank has received the file), the action may not yet have triggered
actual funds availability.
### Refunds
In the event that either the payer or payee determines that the intentional reversal of a successful ACH payment is
desired, a refund may be issued. If a refund is decided upon before a payout is complete, the refund can be created,
and will be processed as soon as the payout is settled.
***
## Payment Speeds
GrailPay offers multiple options for the speed at which payments can move. The processing of ACH payments are dictated
by pre-determined processing windows set by the Federal Reserve. This system allows for payments to be initiated
throughout the day before being processed in the next window.
### Standard ACH
GrailPay processes Standard ACH on a four day settlement schedule, meaning the payout occurs four banking days after
the payment initiation. Actual funds availability to the payee will ultimately be determined by their financial
institution's internal policies.
### Fast ACH
GrailPay processes Fast ACH on a same or next day settlement schedule, depending on what time the payment is initiated.
Actual funds availability to the payee will ultimately be determined by their financial institution's internal policies.
***
## Additional Payment Scenarios
### ACH Returns
ACH payments can fail for a variety of reasons including things like a closed bank account, a debit-blocked bank account,
insufficient funds in the payer's bank account, and even sanctions freezes on bank accounts.
ACH returns are unique in that they occur *after* the initial payment initiation. The overwhelming majority of returns
occur within two banking days of the initial payment initiation, and for B2B ACHs (CCD), are generally limited to this
timeframe. Returns on consumer, or WEB ACHs, also typically occur within two days, but do have a 60 day allowable time
frame from payment initiation for payer disputes.
When doing Standard ACH (four day settlement), returns are typically triggered before a payout has occurred, in which
case the funds from the credit are seamlessly returned to the payer. When doing Fast ACH (same/next-day settlement), it
is more common to see returns triggered after the payout has occurred. In this scenario, GrailPay's system automatically
initiates a clawback, which debits the payee bank account to offset the funds that went back to the payer due to the
return.
You can find additional information on possible [ACH returns in our Resources](../resources/return-codes).
### Notice of Change (NOC)
Occasionally in the course of processing ACH, a Receiving Depository Financial Institution (RDFI) will issue a Notice
of Change, or NOC. These communicate when an ACH entry is outdated or incorrect. The RDFI sends the NOC to the Originating
Depository Financial Institution (ODFI) to notify them of the erroneous account information, in which case we are allowed
six days to update the bank account information.
In most cases when a NOC is issued, an ACH payment will still process successfully. GrailPay will communicate the receipt
of NOCs when they are received, and update the relevant bank account information as is required.
### Clawback
A clawback is triggered automatically when an ACH return occurs after the payout has been processed. In this scenario,
the return has already reversed the funds back to the payer, and the clawback is debiting the merchant bank account to
zero out the difference.
### Reverse Payout
These occur when the payee bank account is not active, or in some rare scenarios, cannot be credited. In such instances,
the funds are sent back to the payer.
For a full list of possible ACH payment behaviors and their associated GrailPay events and statuses, review our
dedicated [Resource page](../resources/transaction-states).
***
## Operations
To ensure the smooth and efficient management of an ACH payments program, GrailPay implements basic transaction rules and
allowable payment volumes within given time frames. These are tailored to the specific requirements of each client ACH
program, and are set during onboarding.
### Origination Limit
Your origination limit is the maximum total allowable amount of ACH payment volume, in dollars, during a rolling 24 hour
time period. **This limit should be actively monitored with internal logic and tooling at all times** to ensure a
disruption-free experience. Failure to do so may result in unexpected payment failures that will require manual
correction.
***
## Testing
To test your integration, connect to GrailPay's sandbox environment at `https://api-sandbox.grailpay.com`. For full
details on environment configuration, refer to our [Sandbox Environment](/docs/technical/sandbox-environment) guide.
### Bank Accounts
When creating people, businesses, or merchants in the sandbox, you can simulate bank accounts using a random 12-digit
account number and one of the following test routing numbers:
* `226078036`
* `221979363`
This setup allows you to test your full integration flow — including onboarding, transactions, and ACH processing —
without interacting with real financial institutions.
### Social Security Numbers (SSNs)
When testing in the sandbox environment, Social Security Numbers (SSNs) must follow specific formatting rules to be
considered valid. The SSN:
* **Cannot** begin with `"000"` or any value in the range `"900–999"`
* **Cannot** have `"00"` as the middle two digits
* **Cannot** end in `"0000"`
Any other valid 9-digit SSN format outside of these constraints is acceptable for testing purposes.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Manage Payouts
Source: https://docs.grailpay.com/docs/technical/payouts/manage-payouts
## Overview
The GrailPay Payments API provides endpoints for retrieving payout information, allowing your systems to monitor, audit,
and reconcile outgoing funds. Payouts represent the credit leg of an ACH transfer and are typically linked to the
original transaction that initiated the flow of funds.
Payout retrieval is handled through two endpoints:
* **List Payouts** — retrieve a filtered, paginated list of payouts
* **Fetch a Payout** — retrieve a single payout by its UUID
These replace the previous entity-scoped endpoints, which required a separate call depending on whether the payout was
made to a Business, Merchant, Person, or Processor. Payout type and recipient are now expressed as filters on a single
unified endpoint.
Payout responses include a `modality` field that tells you exactly how each payout was sent and how quickly it will
arrive. The modality field contains two pieces of information:
* The method used to send the funds (such as ACH or FedNow)
* The speed at which the transfer will complete
This makes it easier to track and report on your payouts, giving you a clearer picture of how money is moving through
our system. You'll see this field in responses from all payout endpoints, as well as in payout webhook events.
The previous V1 and V2 payout endpoints remain supported and documented in our OpenAPI documentation, but they will
be deprecated at a future date. We recommend integrating with the V3 endpoints described on this page.
***
## List Payouts
To retrieve a list of payouts, use this endpoint to return all payout records associated with your account. This
endpoint supports a rich set of query parameter filters, allowing you to refine results by payout type, status,
recipient, related transaction, merchant, amount, date range, ACH trace ID, and return code. Results are paginated and
can be sorted by created date or amount.
Payouts can be filtered by type — `individual`, `batch`, `vendor_fee`, or `standalone` — making it straightforward to
isolate a specific class of disbursement without needing separate endpoints for each.
This is especially useful for:
* Reconciling disbursements across multiple transactions
* Reviewing payout history for a specific recipient or merchant
* Powering payout dashboards and internal reporting tools
* Monitoring payout statuses and ACH return codes
For full request and response definitions, including all available filters, refer to the
[List Payouts OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Payouts/listPayouts).
***
## Fetch a Payout
To retrieve the details of a specific payout, provide the payout UUID. The response includes a complete record of that
disbursement, including its status, amount, modality, associated transactions, and timestamps.
For full request and response definitions, refer to the
[Fetch Payout OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Payouts/showPayout).
To obtain the UUID for a payout, you can either use the [List Payouts](#list-payouts) endpoint or subscribe to the
appropriate payout [Webhook Events](/docs/technical/webhooks/events).
***
## Standalone Payouts
A Standalone Payout is a credit issued directly to a recipient without an associated transaction. Rather than being
funded by the debit leg of a transfer, standalone payouts draw from a pre-funded FBO account that you maintain with
GrailPay.
This is useful for disbursement use cases where funds are not collected from a payer at the time of payment — for
example, rebates, incentive payments, or scheduled disbursements from a maintained balance.
To create a standalone payout, submit the amount details and recipient UUID to the standalone payout endpoint. The
payout is released against your available pre-funded balance and follows the same processing lifecycle and webhook
events as any other payout.
Recipients must be onboarded to GrailPay before a standalone payout can be issued to them. For more information,
refer to our [Onboard a Person](/docs/technical/users/onboard-a-person),
[Onboard a Business](/docs/technical/users/onboard-a-business), or
[Onboard a Merchant](/docs/technical/users/onboard-a-merchant) documentation.
For full request and response definitions, refer to the
[Create Standalone Payout OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Payouts/createStandalonePayout).
Standalone Payouts are not enabled by default and require both an approved account and a pre-funded FBO account. If
you would like to offer this functionality, contact your GrailPay integration support contact to have it enabled on
your account.
Standalone payouts are limited by the available balance in your pre-funded FBO account. A payout that exceeds your
available balance will be rejected.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Create a Refund
Source: https://docs.grailpay.com/docs/technical/refunds/create-a-refund
Refunds in the GrailPay Payments API allow you to reverse a completed transaction by returning funds to the original sender.
This guide walks through the process of creating a refund, outlines the requirements, and explains how to test refund
flows in the sandbox environment.
***
### What is a Refund?
A **Refund** represents a reversal of a previously completed transaction. Once a transaction has been successfully
processed and settled, you may issue a refund to return some or all of the original funds back to the payor. Refunds
are commonly used for resolving customer disputes, cancellations, or error corrections.
Every refund is linked to a specific transaction and has its own lifecycle, including statuses such as `REFUND_PENDING`,
`REFUND_COMPLETE`, or `REFUND_FAILED`. Refunds are processed through the ACH network, similar to standard transactions, and
typically result in a new payout to the original payor.
***
### What is a Batch Refund?
A **Batch Refund** is a structured mechanism for processing multiple refund requests together under a single operation.
Instead of initiating individual refund payments one by one, GrailPay groups confirmed refund requests into batches,
processes them collectively, and facilitates a single fund capture per batch.
This is particularly useful in vendor scenarios where a platform (such as a payment aggregator or facilitator) needs to
approve and fund multiple refunds initiated by their underlying merchants. By batching these refunds:
* GrailPay can consolidate refund amounts by merchant or partner
* The platform receives a shared refund ledger to review all refund requests
* Only confirmed refunds are included in the batch and processed
* A batch UUID is generated to track the group of refunds
* A single debit (refund capture) is initiated against the platform’s funding account for the total amount of the batch
This approach improves operational efficiency, ensures partner-level authorization, and simplifies reconciliation for
multi-vendor platforms.
There is no public API endpoint for creating a batch refund. This is by design as batch refunds require an internal
approval process before they can be processed. Refund requests are initiated individually by vendors, then reviewed
and confirmed through a shared ledger process. Once approved, GrailPay internally generates batch UUIDs to group the
confirmed refunds for processing. This ensures proper authorization, accountability, and fund availability before
initiating any refund captures.
***
### Steps to Create a Refund
Follow these steps to successfully create a refund using the GrailPay Payments API.
***
#### 1. Retrieve the Original Transaction UUID
To create a refund, you’ll need the `uuid` of the transaction you wish to refund. Only transactions that have begun the
settlement process (i.e., have reached a **CAPTURE\_ACH\_PENDING** or later status) are eligible for refunds.
A refund can be initiated any time after the original transaction reaches a **CAPTURE\_ACH\_PENDING** status. However, the refund
itself will not begin processing until the original transaction has fully settled — a process that may take several
business days.
You can use the [Fetch Transaction by UUID](/docs/technical/transactions/manage-transactions#fetch-a-transaction-by-uuid) endpoint to retrieve the
transaction details and confirm eligibility.
***
#### 2. Issue a Refund Request
Once you’ve confirmed the transaction is eligible, you can send a refund request by making a POST call to the refund
endpoint. The request must include the original transaction `uuid` and the `amount` to be refunded.
Refunds can be **partial** or **full**, but the total amount refunded must not exceed the original transaction amount.
Refer to the [Refund a Transaction](https://api.grailpay.com/api/documentation#/Refunds/d88ce5d80658c8999d1ca01633290d01)
OpenAPI documentation for full request and response details.
***
#### 3. Monitor Refund Status
Once submitted, the refund will move through the standard processing lifecycle. You can monitor the status of a refund
y subscribing to the **Refund** webhook events.
We strongly recommend using webhooks to stay updated on refund processing outcomes, especially in production environments.
***
### Testing Refunds
To test refunds in the sandbox environment:
* Ensure the transaction you are refunding has a status of `CAPTURE_ACH_PENDING` or later status
( ie. `PAYOUT_ACH_PENDING`, `IN_PAYOUT`). In sandbox, this is typically simulated shortly after creation.
* Use the same transaction UUID for your refund request.
* You may refund the full amount or a partial amount.
For more sandbox testing details, see the [Technical Overview](/docs/technical/overview#testing) page.
***
### Webhooks
To monitor refund activity in real-time, subscribe to the `Refund` webhook events. This event provides updates on the
status of the refund, including success or failure, timestamps, and metadata.
For more details, visit the [Webhook Events](/docs/technical/webhooks/events) page.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Manage Refunds
Source: https://docs.grailpay.com/docs/technical/refunds/manage-refunds
Managing refunds is a critical part of the ACH lifecycle — especially when it comes to reconciling financial activity,
handling customer requests, and maintaining audit trails. The GrailPay Payments API provides a suite of endpoints to help
you **retrieve and review refund activity** across different levels of granularity: from batch summaries to individual
refund records.
Whether you're building a dashboard, troubleshooting a specific case, or simply auditing past refund activity, these
endpoints give you the visibility and flexibility you need to stay on top of refund flows.
***
## Available Actions
GrailPay currently supports the following operations for managing refunds:
* Retrieve details of a **specific batch refund**
* List **all batch refunds** with support for filtering and pagination
* Fetch **all refunds associated with a specific transaction**
* Retrieve **details about a specific refund**
***
## Retrieve a Batch Refund
You can retrieve a **specific batch refund** by using its unique UUID. This endpoint returns all key information for the
batch — including refund amount, status, processing timestamps, and linked transactions. This is useful when auditing or
reviewing the outcome of a large-scale reversal.
For full details, refer to our OpenAPI documentation:
* [Get Batch Refund](https://api.grailpay.com/api/documentation#/Refunds/63ed85c2621b265ccf4b9a5c4b4ec278)
***
## List All Batch Refunds
This endpoint allows you to fetch **a list of all batch refunds**, with support for filtering by status, date range,
and more. Results are paginated to support large datasets and can be sorted to help you find recent activity quickly.
This is ideal for:
* Reconciling large volumes of refund activity
* Exporting historical refund records
* Powering internal reporting dashboards
For full details, refer to our OpenAPI documentation:
* [List Batch Refunds](https://api.grailpay.com/api/documentation#/Refunds/83fd1801d91a8efd53e356094515a475)
***
## List Refunds
To retrieve a list of refunds, use this endpoint to return all refund records associated with your account. This
endpoint supports a rich set of query parameter filters, allowing you to refine results by transaction, merchant,
status, amount, date range, ACH trace ID, and return code. Results are paginated and can be sorted by created date
or amount.
This is especially useful for:
* Reconciling refunds across multiple transactions
* Tracking partial or multi-stage refund workflows
* Auditing refund history for a specific merchant or transaction
* Monitoring refund statuses and ACH return codes
For full request and response schemas, including available filters, refer to the
[List Refunds OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Refunds/15ab8fb241a8a4e20e23f07b09932965).
***
## Retrieve a Specific Refund
Use this endpoint to fetch a **single refund by its UUID**, giving you detailed insight into a specific refund’s amount,
status, related transaction, and any associated batch.
This is useful for:
* Troubleshooting issues with a specific refund
* Confirming refund success
* Displaying granular refund data in product interfaces
For full details, refer to our OpenAPI documentation:
* [Fetch a Refund](https://api.grailpay.com/api/documentation#/Refunds/fc4444653bc3cccf783d03b44d81124d)
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Sandbox
Source: https://docs.grailpay.com/docs/technical/sandbox-environment
## API Host
* `Base Url` - [https://api-sandbox.grailpay.com/3p/api](https://api-sandbox.grailpay.com/3p/api)
* `Version` - v2
## API Headers
* `API Key` - The bearer auth token [as described here](https://grailpay.readme.io/docs/authentication)
* `Content-Type` - application/json
* `Accept` - application/json
## API Date & Time Format
All dates and times must be requested and responded to in the following format
* `date` - yyyy-mm-dd
* `time` - hh:ii
* `datetime` - yyyy-mm-dd hh:ii:ss
## API Money Format
All amounts and fees are requested and responded in cents.
# Create a Transaction
Source: https://docs.grailpay.com/docs/technical/transactions/create-a-transaction
## What is a Transaction?
A **Transaction** in the GrailPay Payments API initiates the movement of funds between two users — typically a payer ( sender ) and a
payee ( recipient ) — via the ACH network. It represents the debit leg of the transfer, capturing the withdrawal of funds from the
payer’s bank account. Once processed, the transaction will result in a corresponding **Payout**, which reflects the credit
leg — the delivery of funds to the payee. Each transaction is uniquely identified by a uuid and includes metadata such
as the amount, participants, and processing status.
Transactions can represent:
* **Merchant to Merchant** payments
* **Merchant to Business** or **Business to Merchant** payments
* **Merchant to Person** disbursements
* **Person to Merchant** collections
* **Person to Person** payments
Once created, the transaction enters a processing pipeline that will debit the payer's bank account and credit the
payee's bank account, subject to ACH network rules and timing.
***
## Overview
Creating a transaction involves initiating a fund transfer between two parties — a **payer** (sender) and
a **payee** (recipient). Both parties must be onboarded and must have valid, connected bank accounts within the
GrailPay system.
When a transaction is successfully created, the API returns a unique `uuid` that can be used to track its status or
retrieve its details later.
This guide outlines the required fields, entity prerequisites, and common validation scenarios encountered when
initiating a transaction.
***
## Person to Person Payments
GrailPay supports payments directly between two people, allowing funds to move from one individual to another without a
registered Merchant on either side of the transaction.
Please note that fulfilling the compliance obligations applicable to ACH operates differently for Person to Person
payments than payments with Merchants in our system.
To create a Person to Person transaction, both of the following must be true:
* **The payee's KYC status is `approved`.** The individual receiving funds must have successfully passed Know Your
Customer (KYC) identity verification and reached the `approved` status before you create the transaction. Submitting a
person for KYC does not immediately approve them — wait for the [ComplianceStatusChanged](/docs/technical/webhooks/events#compliance-status-changed-event)
webhook event to report an `approved` status. A `submitted`, `rejected`, or any `ON_HOLD_*` status does **not** satisfy
this requirement. See [Submit Person for KYC](/docs/technical/users/manage-users#submit-person-for-kyc) to initiate
verification for a person.
* **Your account is approved for Person to Person payments.** This functionality must be explicitly enabled on your
integration by GrailPay.
Person to Person payments are not enabled by default. If you would like to offer this functionality, contact your
GrailPay integration support contact to have it enabled on your account.
A Person to Person transaction will be rejected if the payee's KYC status is not `approved`, or if your account is
not approved for Person to Person payments.
Before creating the transaction, wait for the payee's KYC to reach an `approved` status, delivered via the
[ComplianceStatusChanged](/docs/technical/webhooks/events#compliance-status-changed-event) webhook event. Any other
status — including `submitted`, `rejected`, or any `ON_HOLD_*` status — does not satisfy this requirement.
All other transaction types continue to require at least one KYB-verified Merchant, as described above.
***
## Destination Account & Batch Payouts
When creating a transaction, you can optionally specify a destination account — the payee's bank account that will
receive the payout — using the `destination_bank_account` field. How GrailPay uses this value depends on whether the
merchant receiving the funds is configured for **Batch Payouts**.
| Merchant configuration | Where the payout is sent |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| **Not** configured for Batch Payouts | The destination account specified during transaction creation (`destination_bank_account`). |
| Configured for Batch Payouts | The merchant's **default bank account**. The destination account specified during transaction creation is not used. |
This is expected system behavior. For merchants configured for Batch Payouts, payouts are **always** sent to the
merchant's default bank account, regardless of any destination account provided at transaction creation. The
destination account you specify is only used when the merchant is **not** configured for Batch Payouts.
For more on how batch and direct payouts are delivered, see
[Scenarios: Direct & Batch Payouts](/docs/resources/scenarios-direct-and-batch-payouts).
To update a Merchant or Business to receive individual payouts instead of Batch Payouts, please refer to
the [Manage Users](/docs/technical/users/manage-users) page.
***
## Transaction Metadata
When creating a transaction, you can optionally include metadata fields that describe the transfer. These values are
passed through to the ACH entries and appear on bank statements and in ACH records, helping both the payer and payee
recognize the purpose of the payment.
| Field | Max Length | Description |
| -------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `company_name` | 16 characters | The originator (company) name displayed on the ACH entry and bank statement. Auto-populated when omitted — see the rules below. |
| `description` | 10 characters | A short descriptor of the transfer's purpose included on the ACH entry. |
| `addenda` | 80 characters | Supplemental free-form information attached to the ACH entry. Only transmitted on CCD (business-to-business) entries. |
All three fields are optional. Any value you send is sanitized before it is used:
* Only letters, numbers, and spaces are kept — any other characters are removed.
* Consecutive spaces are collapsed into a single space.
These fields are **not** truncated. If a value exceeds its maximum length, the API rejects the entire transaction
request rather than trimming the value. Check the length of `company_name`, `description`, and `addenda` before
submitting.
For clean, predictable metadata, send values that are already within the character limits and avoid punctuation or
symbols you need preserved.
### Capture vs. Payout Metadata
Every transfer moves money in two legs, and metadata is applied **independently** to each:
* **Capture** — the debit that pulls funds from the payer's bank account. Uses the values you send at creation.
* **Payout** — the credit that delivers funds to the payee's bank account. Derives its own metadata later, drawing on
the stored transaction values and the payer's profile.
Because the two legs are computed separately, they can display different values, and leaving a field blank at creation
does **not** mean it will be empty on the payout.
### Capture metadata (debit leg)
At creation, the values you provide are applied to the capture ACH entry:
| Field | When you provide a value | When you omit it |
| -------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `company_name` | Used as sent, after sanitization. | Auto-generated from the **payee**: the payee's business/merchant name, otherwise the payee's first and last name, otherwise left empty. |
| `description` | Used as sent, after sanitization. | Left empty on the capture (no fallback at this stage). |
| `addenda` | Stored as sent, after sanitization. | Left empty. |
`addenda` is always stored on the transaction, but it is only transmitted on the outgoing ACH entry for CCD
(business-to-business) entries. On PPD or WEB entries the value is retained but not sent to the bank.
### Payout metadata (credit leg)
When the transaction reaches payout, metadata for the credit ACH entry is recalculated — it does not simply copy the
capture values. Each field falls through the following order until a usable value is found. Because these payout values
are derived by GrailPay rather than submitted in your request, they are sanitized and trimmed to fit each field's limit:
| Field | Fallback order (first match wins) |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `company_name` | 1) the transaction's `company_name` → 2) the payer's business/merchant name → 3) the payer's name → 4) `GrailPay` (default) |
| `description` | 1) the transaction's `description` → 2) the payer's business/merchant name → 3) the payer's name → 4) empty |
| `addenda` | Included only when the payout is sent as CCD (business-to-business), using the transaction's `addenda`. Omitted (`null`) for PPD and WEB payouts. |
Because the payout fills in `company_name` and `description` from the payer's profile when you leave them blank, the
credit entry is rarely empty even if you send no metadata. If you want consistent, predictable values on both legs,
send `company_name` and `description` explicitly.
**Batch Payouts only:** GrailPay offers an optional, account-level configuration — available on request — that
overrides the `company_name` fallback order above and always sends your (the client's) name as the originator
company name on batch payout entries. Contact your GrailPay integration support contact to enable it.
***
## Testing
To test transactions in GrailPay’s sandbox environment, you’ll need two entities:
* One with a connected bank account to act as the payer
* One with a connected bank account to act as the payee
For more information on creating accounts in our sandbox environment, see the [Technical Overview](/docs/technical/overview#testing) guide.
You can provide these details when creating test users via the following endpoints:
* [Onboard a Person](/docs/technical/users/onboard-a-person)
* [Onboard a Business](/docs/technical/users/onboard-a-business)
* [Onboard a Merchant](/docs/technical/users/onboard-a-merchant)
* [Add Bank Account](/docs/technical/bank-accounts/add-bank-account)
This setup allows you to perform end-to-end transaction testing — including creation, settlement, and simulated
failure — without using real financial data.
Most transactions require that at least one party (either the payer or payee) has successfully completed KYB
verification — meaning one side must be a registered Merchant. The exception is Person to Person payments, which
instead require the payee to have completed KYC verification and your account to be approved for the functionality
(see [Person to Person Payments](#person-to-person-payments) above).
***
## Step 1: Confirm Participants Are Onboarded
Before creating a transaction, both the **payer** and **payee** must exist in the system as valid users
(i.e. persons or businesses).
Each must meet the following criteria:
* Successfully onboarded using the appropriate flow
* Possess a valid `uuid`
* Have an active, connected bank account (manual or Plaid-linked)
You will reference these participants using:
* `payer_uuid` — the sender of funds
* `payee_uuid` — the recipient of funds
If either party is missing or has an invalid account, the transaction will be rejected.
***
## Step 2: Determine Transaction Type and Amount
Each transaction must include:
* `amount` — The dollar value to be transferred
* `speed` — Indicates processing tier (e.g., `standard` or `sameday`, if available in your integration)
Optional fields include:
* `client_reference_id` — Optional reference ID from your system
* `description` — Optional metadata for reporting
All amounts must be denominated in USD and specified in cents (e.g., \$10.00 = 1000).
***
## Step 3: Submit the Transaction
To initiate the transaction, send a `POST` request to the following endpoint: `/3p/api/v1/transactions` with the required
fields. Upon success, the API will return a `uuid` representing the newly created transaction. This can be used to
fetch status updates or cancellation requests.
### Character Limits
When creating a new transaction, certain fields are subject to strict character limits. These constraints are imposed by
the ACH network and our banking partners to ensure compatibility with downstream systems:
| Field | Character Limit |
| -------------- | --------------- |
| `company_name` | 16 characters |
| `description` | 10 characters |
| `addenda` | 80 characters |
Make sure your values do not exceed these limits, or the transaction may be rejected during processing.
See the [Create Transaction OpenAPI documentation](https://api.grailpay.com/api/documentation#/Transactions/f96034804914c77cf08a89c767ec8be4)
for the full schema, including all required and optional fields.
***
## Step 4: Monitor the Transaction Status
Once submitted, a transaction may move through multiple states depending on processing speed, ACH timing, and risk evaluation.
Possible states include:
* `CAPTURE_PENDING`
* `CAPTURE_ACH_PENDING`
* `CAPTURE_ACH_FAILED`
* `CANCELED`
* `IN_PAYOUT`
To see a full list of states and their definitions, refer to the [Transaction States](/docs/resources/transaction-states) documentation.
To monitor the state of a transaction, you can use one of the following methods:
* **Webhook Notifications**: Set up webhooks to receive real-time [Webhook Events](/docs/technical/webhooks/events) on transaction status changes.
* **API Polling**: Use the `GET /3p/api/v2/transactions/{transaction_uuid}` endpoint to fetch the current status of a specific transaction.
To fully monitor the lifecycle of a funds transfer, it’s important to understand that a Transaction only represents
the debit leg of the movement. The Payout, which delivers funds to the payee, follows its own processing lifecycle
and status updates.
We **strongly recommend** subscribing to both the Transaction and Payout webhook events to accurately track the complete
flow of funds — from initiation to settlement.
***
## Questions?
If you're encountering any issues, contact your GrailPay integration support contact or open a support request via the portal.
***
## Resources
Below you will find some helpful resources to assist with understanding how transactions work in GrailPay.
* [Transaction States](/docs/resources/transaction-states)
* [ACH Return Codes](/docs/resources/return-codes)
* [Scenarios: Direct & Batch Payouts](/docs/resources/scenarios-direct-and-batch-payouts)
* [Transaction Queues](/docs/resources/transaction-queues)
# Manage Transactions
Source: https://docs.grailpay.com/docs/technical/transactions/manage-transactions
## Overview
The Manage Transactions section of the GrailPay Payments API allows you to programmatically interact with existing transactions after they have been created. These endpoints enable you to:
* Retrieve a single transaction by UUID
* Fetch a list of transactions with filtering and pagination
* Search returns — transactions or payouts that failed in the ACH network
* Review clawbacks that recover funds after a return following a payout
* Pause, resume, or cancel a transaction before it is fully settled
These capabilities are critical for building robust payment workflows. Whether you're reconciling payments, reviewing transaction histories, intervening in suspicious activity, or responding to user-submitted errors, this suite of tools gives you the flexibility to safely manage ACH transactions.
***
## Fetch a Transaction by UUID
This operation retrieves a specific transaction using its unique identifier. It is commonly used for:
* Verifying the current status of a transaction
* Reviewing transaction details for auditing or customer support
* Debugging issues related to processing, metadata, or reconciliation
The response includes full details of the transaction, including payer and payee info, amount, processing status, timestamps, and associated payout data if applicable.
Refer to the [Fetch Transaction by UUID](https://api.grailpay.com/api/documentation#/Transactions/ee52b4fd2a708352da760a353bfaca36) section in our API documentation for full request and response details.
***
## List Transactions
When managing a large volume of ACH activity, you may need to review a filtered set of transactions—whether for reporting, operational review, or anomaly detection.
The API supports flexible filtering across attributes such as:
* Date ranges (e.g., last 30 days)
* Payer or payee identifiers
* Account association
* Transaction status (e.g., pending, paused, completed)
You can paginate through results and optionally sort by attributes like `created_at`. This functionality is commonly used to populate dashboards, generate audit logs, or feed data into internal tooling.
Refer to the [List Transactions](https://api.grailpay.com/api/documentation#/Transactions/5aa060d7d961b95ba524221445ac18a7) section of our API documentation for query parameters and examples.
***
## List Returns
A **return** is any transaction or payout that failed after entering the ACH network—for example, a debit that was
returned for insufficient funds (R01) or a payout that could not be credited to the receiving account. The Search Returns
endpoint provides a single, paginated view of these failures across both legs of a transfer:
* **Capture leg** (`capture`) — the debit (transaction) leg was returned.
* **Payout leg** (`payout`) — the credit (payout) leg was returned.
When the `filter[leg]` parameter is omitted, both capture and payout returns are included.
Each return record links back to its related objects—the `transaction`, `payout`, `clawback`, and `reverse_payout` where
applicable—along with the `entity`, `payor`, and `payee` involved, so you can reconcile a failure without additional
lookups. This functionality is commonly used to:
* Build return and exception dashboards or daily reconciliation reports
* Trigger retry or customer-outreach workflows based on the [ACH return code](/docs/resources/return-codes)
* Investigate a specific failure by transaction, payout, entity, or trace ID
The endpoint supports flexible filtering across attributes such as:
* **Leg** — `capture` or `payout`
* **ACH return code** — a single code (`filter[ach_return_code]`) or several comma-separated codes (`filter[ach_return_codes]`)
* **Amount** — in cents, with dynamic operators (e.g., `filter[amount]=>10000`, `filter[amount]=<=50000`)
* **Related object UUIDs** — transaction, payout, clawback, reverse payout, or entity (payor/payee)
* **Date range** — `filter[start_date]` and `filter[end_date]`, matched against the ACH failure date (`YYYY-MM-DD`)
* **Identifiers** — bank ID, ACH trace ID, and (for processors) vendor ID
You can sort by `ach_failed_at` or `amount`—prefix the field with `-` for descending order—and paginate with `page` and `per_page`.
A "return" here is a NACHA ACH return, identified by its [return code](/docs/resources/return-codes) (e.g., R01, R10).
It is distinct from a [Refund](/docs/technical/refunds/create-a-refund), which is a new credit you initiate to send
funds back to a payer.
Refer to the [Get All Returns](https://api.grailpay.com/api/documentation#/Returns/af6624a2f738213377a98bba0e4b1da5) section in
the API documentation for the full list of query parameters and the response schema.
***
## Clawbacks
A **clawback** is triggered automatically when an ACH return occurs *after* a payout has already been sent—the return
reverses the funds back to the payer, and the clawback debits the payee to offset the difference. Clawbacks move through
their own lifecycle (`CLAWBACK_PENDING` → `CLAWBACK_ACH_PENDING` → `CLAWBACK_COMPLETE`, with `CLAWBACK_ACH_FAILED` and
`CLAWBACK_CANCELLED` as terminal exceptions). See [Clawback](/docs/resources/transaction-states#clawback) in Transaction
States for the full status reference and the webhook that accompanies each step.
The following read-only endpoints let you list clawbacks and retrieve a single clawback for reporting, reconciliation, and
support workflows. Each clawback record includes its `status`, `amount` (in cents), `clawback_type` (`transaction` or
`vendor_fee`), the `payee` being debited, the originating `ach_return_code`, and identity pointers back to the related
`transaction` and `payout`.
### List Clawbacks
Returns a paginated list of clawbacks visible to the authenticated user. Supported filters include:
* **Status** — `filter[status]` (e.g., `CLAWBACK_PENDING`, `CLAWBACK_ACH_FAILED`, `CLAWBACK_COMPLETE`)
* **Amount** — in cents, with dynamic operators (e.g., `filter[amount]=>100`, `filter[amount]=<=500`)
* **Related object UUIDs** — parent transaction (`filter[transaction_uuid]`), merchant (`filter[merchant_uuid]`), or payee (`filter[payee_uuid]`)
* **Identifiers** — clawback UUID (`filter[uuid]`), ACH trace ID (`filter[ach_id]`), and client reference ID (`filter[client_reference_id]`, partial match)
* **Date range** — `filter[start_date]` and `filter[end_date]`, matched against the clawback creation date (`YYYY-MM-DD`)
You can sort by `created_at` or `amount`—prefix the field with `-` for descending order—and paginate with `page` and `per_page`.
Refer to the [Get All Clawbacks](https://api.grailpay.com/api/documentation#/Clawbacks/f98cf1b964d449179529fd2560d4ed04)
section in the API documentation for the full list of query parameters and the response schema.
### Fetch Clawback by UUID
Retrieves the details of a single clawback, along with identity pointers to its related transaction and payout. The
clawback UUID is generated when the clawback is created.
Refer to the [Get Clawback](https://api.grailpay.com/api/documentation#/Clawbacks/90e33fdd2fe4f73e8654d465955fcf54)
section in the API documentation for request and response details.
***
## Cancel a Transaction
You may cancel a transaction if it is no longer valid or desired—such as in the case of incorrect bank details, suspected fraud, or a user-initiated reversal.
In previous versions of the API, transactions could only be canceled prior to debit creation. In API v3, cancellations are supported **up to the point when the payout (credit) is created**, giving you greater control over timing and exception handling.
Given the multi-party file-based nature of ACH, GrailPay cannot truly cancel a debit once the file has entered the Sent state. At this point, the debit file has been sent to the ACH network and will proceed according to the rules and regulations of ACH.\
\
After a debit enters the Sent State, it will process and settle as expected according to the applicable rules and regulations of ACH. GrailPay's cancellation functionality acts as a recuperation mechanism that creates a reverse payout to reverse the funds of the transfer once the debit has settled. In the event that a debit initially succeeds but then fails due to an ACH return, a reverse payout will not be needed and will not be created. If an ACH return does not occur within the allowable window, a reverse payout will automatically be created given the cancellation.
There are three key cancellation paths:
* **Before the debit is created**: The transaction is immediately canceled.
* **After the debit is created but before settlement**: The transaction enters an `AWAITING_CANCELLATION` state. Once the debit settles, a Reverse Payout is automatically triggered, and the transaction is finalized as canceled.
* **After the debit has settled**: A Reverse Payout is immediately triggered, and the transaction is canceled once the reversal completes.
When a transaction enters `AWAITING_CANCELLATION`, the [TransactionAwaitingCancellation](/docs/technical/webhooks/events#transaction-awaiting-cancellation-event) webhook is fired. Once cancellation is finalized, the [TransactionCanceled](/docs/technical/webhooks/events#transaction-canceled-event) webhook is sent.
Refer to the [Cancel Transaction](https://api.grailpay.com/api/documentation#/Transactions/9dc27221eac690253441292456a4a217) section in the API documentation for request and response formats.
***
## Pause a Transaction
Pausing a transaction allows you to halt processing before the funds are moved. This is particularly useful in scenarios involving:
* Fraud review queues
* Manual approval workflows
* Customer service hold scenarios
A paused transaction remains in the system but will not advance to processing until explicitly resumed. This can give internal teams time to review details or await additional information.
When a transaction is paused, the [TransactionPaused](/docs/technical/webhooks/events#transaction-paused-event) webhook is emitted. Transactions that remain paused for more than 5 days are automatically canceled by the platform to prevent indefinite holds.
Refer to the [Pause Transaction](https://api.grailpay.com/api/documentation#/Transactions/6d96bf444c9ac9662a061d0b097fe547) section in the API documentation for implementation details.
***
## Resume a Transaction
Resuming a transaction that was previously paused returns it to the normal processing lifecycle. This is typically used after:
* A manual review process has completed
* A customer has confirmed transaction details
* A fraud flag has been cleared
The transaction picks up from where it left off, continuing processing as if uninterrupted.
When a transaction is resumed, the [TransactionResumed](/docs/technical/webhooks/events#transaction-resumed-event) webhook is emitted.
Refer to the [Resume Transaction](https://api.grailpay.com/api/documentation#/Transactions/2ecb07bd1c26a607035ddf040f3376f4) section in the API documentation for more information.
***
## Transaction Status Flow
The following table outlines how different actions affect transaction status and which webhooks are triggered:
| Action | Resulting Status | Webhook Emitted |
| --------------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Pause | `PAUSED` | [TransactionPaused](/docs/technical/webhooks/events#transaction-paused-event) |
| Resume | Resumes prior status (e.g., `PENDING`) | [TransactionResumed](/docs/technical/webhooks/events#transaction-resumed-event) |
| Cancel (before debit) | `CANCELED` | [TransactionCanceled](/docs/technical/webhooks/events#transaction-canceled-event) |
| Cancel (after debit, before settlement) | `AWAITING_CANCELLATION` → `CANCELED` | [TransactionAwaitingCancellation](/docs/technical/webhooks/events#transaction-awaiting-cancellation-event), then [TransactionCanceled](/docs/technical/webhooks/events#transaction-canceled-event) |
| Cancel (after debit settled) | `CANCELED` (via Reverse Payout) | [TransactionCanceled](/docs/technical/webhooks/events#transaction-canceled-event) |
***
## Timestamps & Auditing
The platform includes additional metadata to support traceability and compliance:
* A `canceled_at` timestamp is recorded whenever a transaction is canceled
* An `ach_canceled_at` timestamp is used to indicate ACH-level reversals
* All transitions are available via the fetch endpoint for audit review
***
## Error Handling
Transactions cannot be paused, resumed, or canceled if:
* They are already in a terminal state (`CANCELED`, `COMPLETED`, `FAILED`)
* The payout has already been created
* They are already in the requested state (e.g., trying to pause an already paused transaction)
In these cases, the API will return a clear `403` or `404` response with a detailed error message to assist in troubleshooting.
***
## Questions?
If you're encountering any issues, contact your GrailPay integration support contact or open a support request via the portal.
***
## Resources
* [Transaction States](/docs/resources/transaction-states)
* [ACH Return Codes](/docs/resources/return-codes)
* [Scenario: Direct Payout](/docs/resources/example-direct-payout)
* [Scenario: Batch Payout](/docs/resources/example-batch-payout)
# Manage Users
Source: https://docs.grailpay.com/docs/technical/users/manage-users
## Overview
In the GrailPay Payments API, a user can take the form of a **Person**, **Business**, or **Merchant**. These entities work together to
represent the full lifecycle of participants in your ACH flow — from individuals initiating transactions, to businesses
receiving funds, to merchants operating within a platform.
This guide provides a centralized reference for managing users across all three entity types. It includes endpoints for:
* Updating, listing, and retrieving people
* Updating, listing, and retrieving businesses
* Updating, listing, and retrieving merchants
* Deactivating and reactivating merchants
* Deleting a person
Each user record is uniquely identified by a UUID, and many of these endpoints return rich data models, including
associated relationships such as bank accounts, linked entities, and KYB status (where applicable). When deleting a
user (such as a person), the record is logically deleted — meaning it is hidden from the API but retained in the system
for compliance and auditing purposes.
Use this guide to query and manage the users in your platform efficiently.
***
## People
In the GrailPay Payments API, a Person represents an individual user within your platform. A person may be directly involved
in sending or receiving funds, or they may be linked to a Business or Merchant entity as part of an onboarding or
transaction workflow.
This flexible structure allows you to model a wide range of real-world scenarios—from independent users to employees or
representatives acting on behalf of a larger organization. Person records support essential attributes such as name,
contact information, and optional associations to other entities, enabling rich identity and relationship management
across your integration.
A Person can be associated with a Business or Merchant. When retrieving a single Person or
listing all People, there is a relations object in the response that will contain the UUID of the Business or Merchant
associated with that Person.
### List People
To retrieve a list of people, you can use the endpoint that returns all people associated with your vendor. This endpoint
allows you to fetch a paginated list of people, including their UUIDs, names, and email addresses. For complete request
and response schemas, refer to the [List People OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/38b563d2d762e16516d6e2f603607498).
### Fetch a Person
To retrieve a specific person, you will need the UUID of the person you want to retrieve. This endpoint allows you to
fetch detailed information about a person, including their name, email, phone number, and the vendor they are associated
with. For complete request and response schemas, refer to the [Fetch a User OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/47b283e94efa8d2a514c2c9ef98f6077).
### Update a Person
To update a person, you must supply the UUID of the person record you wish to modify along with the fields you intend to
change. This endpoint allows you to update basic identifying information such as name, email, or phone number.
Only fields explicitly passed in the request will be updated — all other fields will remain unchanged. This ensures you
can make partial updates without affecting the full record.
For complete request and response schemas, refer to the [Update Person OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/47c342b6509a1330c4e989b172997b71).
### Delete a Person
To delete a specific person, you will need the UUID of the person you want to delete. This endpoint allows you to
remove a person from the system, which will also make any associated information such as bank accounts, transfers,
and related businesses or merchants inaccessible. For complete request and response schemas, refer to
the [Delete User OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/92a24e030e4c373aa3318a0c883df936).
### Submit Person for KYC
To submit a person for KYC (Know Your Customer) verification, send a POST request to the KYC endpoint with the person's
UUID. KYC verification confirms the identity of an individual and is required for a person to act as the payee in a
[Person to Person transaction](/docs/technical/transactions/create-a-transaction#person-to-person-payments).
This endpoint initiates the verification process. It does not return a verification result — once submitted, the person
proceeds through KYC evaluation, and the outcome is delivered via the
[ComplianceStatusChanged](/docs/technical/webhooks/events#compliance-status-changed-event) webhook event. Submission
alone does not make the person eligible to receive funds: you must wait for the `ComplianceStatusChanged` webhook to
report an `approved` status before using the person as the payee in a Person to Person transaction.
For complete request and response schemas, refer to the [Submit Person for KYC OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/1c835cb00622e64c696c8b000a2cbf6b).
Do not create a Person to Person transaction immediately after submitting a person for KYC. KYC evaluation is
asynchronous — the person is not yet approved when the request returns. Wait for the
[ComplianceStatusChanged](/docs/technical/webhooks/events#compliance-status-changed-event) webhook to report an
`approved` status before using them as a payee, otherwise the transaction will be rejected.
A person only needs to complete KYC when they will receive funds in a Person to Person payment. People who
participate solely as payers, or who transact with a Merchant, do not require KYC.
### Person Statuses
Each person record within the GrailPay Payments API includes a `status` field that reflects their current standing within
the system. This status determines whether they are permitted to participate in transactions and helps guide
workflows related to compliance, fraud mitigation, and lifecycle management.
Below is a list of possible statuses and their meanings:
| Status | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `APPROVED` | The person is fully verified and approved to send or receive funds. |
| `REJECTED` | The person has been rejected and cannot participate in any transactions. This is a status we add manually and signifies a user that cannot be reinstated. |
| `ON_HOLD_FRAUD_PAYER` | A fraud-related return has been reported by the payer. The person is on hold and cannot transact. You must reach out to customer support to initiate an internal review to reinstate the user. |
| `ON_HOLD_FRAUD_PAYEE` | A fraud-related return has been reported by the payee. The person is on hold and cannot transact. You must reach out to customer support to initiate an internal review to reinstate the user. |
| `ON_HOLD_SANCTIONS` | The person has been flagged for potential sanctions risk. They are on hold and cannot transact. You must reach out to customer support to initiate an internal review to reinstate the user. |
| `SCHEDULED_FOR_DELETE` | A deletion request has been received. The person is scheduled for deletion and cannot transact. |
If a person enters a restricted status such as `ON_HOLD_FRAUD_PAYER`, `ON_HOLD_SANCTIONS`, or `ON_HOLD_FRAUD_PAYEE`,
your system will receive a [PersonStatusChanged](/docs/technical/webhooks/events#person-status-changed-event) webhook
event with details about the change, including reason and suggested next steps. In most cases, you will need to contact
our support team, at [support@grailpay.com](mailto:support@grailpay.com), to resolve the issue and restore the person's
ability to transact.
While the status updates will be delivered via webhook, you can also at any time source the status of a person via
calls to the [Fetch Person](https://api.grailpay.com/api/documentation#/Users/47b283e94efa8d2a514c2c9ef98f6077) or
[List People](https://api.grailpay.com/api/documentation#/Users/38b563d2d762e16516d6e2f603607498) endpoints.
***
## Businesses
In the GrailPay Payments API, a Business is a simple entity type that represents an organization capable of sending or
receiving funds. Unlike Merchants, Businesses do not undergo KYB (Know Your Business) verification. However, they are
modeled separately from People to ensure proper ACH classification and compliance with NACHA regulations.
A Business is automatically associated with a Person when it is onboarded. When retrieving a single Business or
listing all Businesses, there is a relations object in the response that will contain the UUID of the Person associated
with that Business.
### List Businesses
To retrieve a list of all businesses associated with your API token, use the list endpoint. This will return a paginated
array of business records including attributes such as UUID, legal name, and timestamps.
For full request and response definitions, refer to the [List Businesses OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/ba480005cebe89606b2f65d95d3c2585).
### Fetch a Business
To fetch details for a specific business, provide the business UUID. The response will include core attributes and
metadata related to the business entity.
For full request and response definitions, refer to the [Fetch a Business OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/d1c75dfe72809303cc7486abbf4b8d1b).
### Update a Business
To update an existing business, send a PATCH request with the updated fields—such as name, address, or contact
information. This helps maintain accurate records for downstream ACH processing and reporting.
While transactions are typically created using the UUID of the Person associated with a Business, this endpoint
specifically requires the UUID of the Business entity itself.
For full request and response definitions, refer to the [Update a Business OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/a176ef962d231a555656d062a2a9d693).
***
## Merchants
In the GrailPay Payments API, a Merchant is a specialized type of Business that has completed KYB (Know Your Business)
verification. KYB is a regulatory requirement that ensures the identity and legitimacy of a business entity before it
can participate in financial transactions. At least one party in every transaction must be KYB-verified, which is why
Merchants play a critical role in GrailPay's compliance and payment workflows.
Although Merchants are technically a kind of Business, they require additional data during onboarding and are subject to
stricter validation rules. This distinction allows GrailPay to maintain compliance while enabling robust and flexible
transaction models.
A Person is automatically associated with a Merchant when it is onboarded. When retrieving a single Merchant or
listing all Merchants, there is a relations object in the response that will contain the UUID of the Person associated
with that Merchant.
### Compliance Status
Each Merchant record includes a `compliance_status` object that provides a complete view of the Merchant's eligibility
to transact. This object contains the computed Compliance Status along with the underlying verification results that
determine it. The `kyb` field reflects the status of the Merchant's business verification, while the `kyc` array
contains the verification status for each Beneficial Owner associated with the Merchant. The top-level `status` field
represents the overall computed Compliance Status, which must be `approved` for the Merchant to be eligible to transact.
For a detailed explanation of compliance requirements, verification outcomes, and corrective actions, refer to
the [Compliance Status Overview](/docs/resources/compliance-status-overview) guide.
### Example Response
```json theme={"system"}
{
"status": "rejected",
"kyb": {
"status": "approved",
"message": null,
"updated_at": "2025-12-11 13:59:21"
},
"kyc": [
{
"uuid": "7f4e8a02-3d5a-4c3b-b823-d8c3125b3f8d",
"first_name": "John",
"last_name": "Doe",
"status": "approved",
"message": null,
"updated_at": "2025-12-11 13:59:21"
},
{
"uuid": "9e2a7cb7-34c1-4c52-a0df-4f9f5704e6e2",
"first_name": "Jane",
"last_name": "Doe",
"status": "rejected",
"message": "Invalid Address",
"updated_at": "2025-12-11 13:59:21"
}
]
}
```
### Fields
#### Top Level
| Field | Type | Description |
| -------- | ------ | ---------------------------------------------------------------------------------------------------------------- |
| `status` | string | The computed Compliance Status for the Merchant. Possible values: `pending`, `in_review`, `approved`, `rejected` |
| `kyb` | object | The KYB verification result for the Merchant's business entity. |
| `kyc` | array | An array of KYC verification results for each Beneficial Owner associated with the Merchant. |
#### KYB Object
| Field | Type | Description |
| ------------ | ----------- | -------------------------------------------------------------------------------------------- |
| `status` | string | The KYB verification status. Possible values: `pending`, `in_review`, `approved`, `rejected` |
| `message` | string/null | If rejected, contains the reason for rejection. Otherwise `null`. |
| `updated_at` | string | Timestamp of the most recent status update. |
#### KYC Array Items
Each item in the `kyc` array represents a Beneficial Owner and their KYC verification result.
| Field | Type | Description |
| ------------ | ----------- | -------------------------------------------------------------------- |
| `uuid` | string | The unique identifier of the Beneficial Owner. |
| `first_name` | string | The first name of the Beneficial Owner. |
| `last_name` | string | The last name of the Beneficial Owner. |
| `status` | string | The KYC verification status. Possible values: `approved`, `rejected` |
| `message` | string/null | If failed, contains the reason for failure. Otherwise `null`. |
| `updated_at` | string | Timestamp of the most recent status update. |
In the example above, the computed `status` is `rejected` even though the KYB status is `approved`. This is because
one of the Beneficial Owners (Jane Doe) has a KYC status of `rejected`. A Merchant is rejected if **either** KYB
fails **or** any Beneficial Owner fails KYC.
### List Merchants
To retrieve a list of all merchants associated with your API token, use the list endpoint. The response will return a
paginated array of merchant records, including their UUIDs, legal names, and KYB status.
For full request and response definitions, refer to the [List Merchants OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/e7ddae992283223ff98ce4cdea429ce6).
### Fetch a Merchant
To fetch details for a specific merchant, provide the merchant UUID. The response will include all core attributes, as
well as KYB-related fields and associated metadata.
For full request and response definitions, refer to the [Fetch A Merchant OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/c262225bb46c22d700bd9d78a6d1f8bb).
### Update a Merchant
To update an existing merchant, send a PATCH request to the update endpoint with any updated fields—such as legal name,
contact information, or address.
For more information about KYB verification and requirements, refer to the [KYB Overview](/docs/resources/kyb-overview) guide.
When testing in the Sandbox environment, Social Security Numbers (SSNs) must follow specific formatting rules to be
considered valid. The SSN:
* **Cannot** begin with `"000"` or any value in the range `"900–999"`
* **Cannot** have `"00"` as the middle two digits
* **Cannot** end in `"0000"`
Any other valid 9-digit SSN format outside of these constraints is acceptable for testing purposes.
If a merchant's KYB status needs to be corrected or additional data is required to pass KYB checks, this endpoint
should be used to resubmit or correct their business information.
While transactions are typically created using the UUID of the Person associated with a Merchant, this endpoint
specifically requires the UUID of the Merchant entity itself.
For full request and response definitions, refer to the [Update A Merchant OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/6680b96fb19eb86c8aea7805cbada282).
### Deactivate a Merchant
To deactivate a merchant, send a POST request to the deactivate endpoint with the merchant's UUID. Deactivating a
merchant sets the merchant to an inactive state, preventing it from initiating new transactions.
This is useful for temporarily suspending a merchant's ability to transact without removing any data from the system.
Deactivation is **non-destructive** — no data is deleted or modified, and any existing transaction history remains
fully intact. Active transactions in progress at the time of deactivation are not automatically cancelled.
This endpoint is **idempotent**. If a merchant is already inactive, calling the deactivate endpoint will return a
successful response without producing any side effects.
```json theme={"system"}
{
"message": "This merchant has been deactivated and will no longer be able to transact"
}
```
This endpoint requires the UUID of the Merchant entity itself, not the UUID of the associated Person.
For full request and response definitions, refer to the [Deactivate Merchant OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/fd7adf2ef947515bd071a0ab67ffc342).
### Reactivate a Merchant
To reactivate a previously deactivated merchant, send a POST request to the activate endpoint with the merchant's UUID.
Reactivating a merchant restores its ability to initiate transactions.
Reactivation does not alter any historical status records or past transactions — it simply sets the merchant back to an
active operational state going forward.
This endpoint is **idempotent**. If a merchant is already active, calling the activate endpoint will return a
successful response without producing any side effects.
```json theme={"system"}
{
"message": "This merchant has been reactivated and is able to transact"
}
```
This endpoint requires the UUID of the Merchant entity itself, not the UUID of the associated Person.
For full request and response definitions, refer to the [Activate Merchant OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/f800ac42a34863bcf448d8400b33d6f7).
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Onboard a Business
Source: https://docs.grailpay.com/docs/technical/users/onboard-a-business
## What is a Business?
A **Business** in the GrailPay Payments API represents a business entity who can send or receive ACH transfers. Each business is
uniquely identified by a `uuid` associated with the user, which is returned upon successful onboarding and is required for
future operations such as linking bank accounts and initiating transfers.
Onboarding a business is a core part of working with the GrailPay Payments API. This guide outlines the business onboarding
workflow and provides context for common use cases. For complete request and response schemas, refer to
the [OpenAPI documentation](https://api.grailpay.com/api/documentation#/Users/999c42d45d89d2db1aa6f3585c95a004).
A business is a specialized type of person in the GrailPay Payments API. While the onboarding process is similar to
that of a person, it includes additional fields and requirements. If you are transferring funds on behalf of a
business entity, it must be onboarded using the business registration flow to meet compliance and regulatory
obligations.
***
## Testing
When testing in GrailPay’s sandbox environment, you can create a business as usual. To include a bank account, simply
use any random 12-digit account number along with one of our supported test routing numbers.
For more details, refer to the [Technical Overview](/docs/technical/overview#testing) guide.
***
## Step 1: Collect Business Information
To create a business we need to collect information about the point of contact and the information about the business
itself, you can provide any combination of the following fields:
Point of Contact Information:
* First name ( point of contact for the business )
* Last name ( point of contact for the business )
* Email
* Phone
Business Information:
* Name ( legal name of the business )
* Address ( business address )
While only a bank account is required to create a business, we **strongly recommend** supplying full information to
ensure a smooth onboarding experience and help meet regulatory and compliance standards.
See [OpenAPI documentation](https://api.grailpay.com/api/documentation#/Users/999c42d45d89d2db1aa6f3585c95a004) for the full
payload and optional parameters such as **client\_reference\_id**.
***
## Step 2: Link to a Merchant for Billing Attribution
If you want to associate billable events with a merchant, include the **billing\_merchant\_user\_uuid** in your request
payload. This value should reference a user tied to a KYB-verified merchant. You will receive this UUID when you onboard
the merchant using the [Onboard a Merchant](onboard-a-merchant) endpoint.
**Field:**
`billing_merchant_user_uuid` — UUID of the merchant user that should receive billing attribution.
This enables GrailPay to accurately report on merchant usage and ensures billing traceability across your organization.
While adding a billing user uuid is not required, we **strongly recommend** supplying this information to ensure
accurate tracking of usage for your internal billing purposes.
***
## Step 3: Provide a Bank Account
You can onboard a person with or without a connected bank account. If you choose to provide one, we support two methods:
### Option A: Link a Plaid Account
If you're using Plaid for bank account linking, include the following fields:
* `bank_account.plaid.access_token`
* `bank_account.plaid.account_id`
For integration details, refer to the [OpenAPI documentation](https://api.grailpay.com/api/documentation#/Users/999c42d45d89d2db1aa6f3585c95a004).
### Option B: Manual Entry
You can also onboard a person using manually provided account details:
* `account_number`
* `routing_number`
* `account_name`
* `account_type` (`checking` or `savings`)
#### Real-Time Validation (Recommended)
To reduce fraud risk and ensure funds can be successfully routed, we recommend enabling real-time bank account validation
during onboarding. This feature is powered by our [Account Intelligence](/docs/risk/bank-accounts/account-intelligence) product,
which provides intelligent validation of routing and account numbers at the time of submission.
To enable this, include an actions object in your onboarding request:
```json Success (200) theme={"system"}
{
"actions": {
"account_intelligence": {
"version": "v3",
"name_match": true
}
}
}
```
This validation step confirms that the provided routing and account numbers are both valid and correctly formatted. If
name\_match is enabled, it also checks that the account holder’s name aligns with the expected identity. In addition to
these checks, the response includes a **Risk Score** and a set of decisioning insights that explain how the account was
evaluated.
To understand the full structure of the response and how to interpret the results, refer to
our [Account Intelligence](/docs/risk/bank-accounts/account-intelligence#response) documentation.
Although optional, enabling this validation is strongly recommended for all production integrations to reduce failure
rates and improve payout reliability.
***
## Step 4: Review the Response
Upon success, you’ll receive a response containing:
* The `uuid` of the person related to the business. This uuid is required for future operations such as linking bank accounts and initiating transfers.
* Echoed values such as name and address
* Status of the onboarding
* An account intelligence object (if validation was requested)
Full response schema is available in the [OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/999c42d45d89d2db1aa6f3585c95a004).
***
## Try It Out
You can use the [API Explorer](https://api.grailpay.com/api/documentation#/Users/999c42d45d89d2db1aa6f3585c95a004) to
test onboarding flows interactively. This tool allows you to enter a payload, submit a request, and view the response
in real time.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Onboard a Merchant
Source: https://docs.grailpay.com/docs/technical/users/onboard-a-merchant
Onboarding a merchant is a core part of working with the GrailPay Payments API. This guide outlines the merchant onboarding
workflow and provides context for common use cases. For complete request and response schemas, refer to
the [OpenAPI documentation](https://api.grailpay.com/api/documentation#/Users/dc8054b7f4b80775e0e4cf97eaf0f196).
## What is a Merchant?
A **Merchant** in the GrailPay Payments API is a business entity that has met GrailPay's compliance requirements
and is eligible to receive or process ACH payments. Merchants typically operate within a vendor's
ecosystem and are often attributed to specific billing events and revenue-sharing flows.
Once onboarded, a merchant is uniquely identified by a `uuid` associated with the user record. This identifier is
required for actions such as initiating attributing billable events and retrieving usage data.
A merchant is a specialized type of business in the GrailPay Payments API. While the onboarding process is similar to
that of a business, it includes a significant number of additional fields and requirements. If you are transferring funds on behalf of a
merchant entity, it must be onboarded using the merchant registration flow to meet compliance and regulatory
obligations. When creating transfers, at least one side of that transfer must be associated with a compliance-verified merchant.
### Compliance Requirements
A Merchant is not fully onboarded until all compliance checks have been completed. Before a Merchant can process
transactions, they must pass both KYB (Know Your Business) verification for the business entity and KYC (Know Your Customer)
verification for each Beneficial Owner. Until these checks are complete, the Merchant's Compliance Status will remain
**Pending**, and they will be unable to transact.
These verification processes run asynchronously after the initial onboarding request is submitted. To monitor a
Merchant's ability to transact, you should subscribe to the [ComplianceStatusChanged](/docs/technical/webhooks/events#compliance-status-changed-event)
webhook event, which will notify your system whenever a Merchant's Compliance Status changes.
For a detailed explanation of compliance requirements, verification outcomes, and corrective actions, refer to
the [Compliance Status Overview](/docs/resources/compliance-status-overview) guide.
When testing in the Sandbox environment, Social Security Numbers (SSNs) must follow specific formatting rules to be
considered valid. The SSN:
* **Cannot** begin with `"000"` or any value in the range `"900–999"`
* **Cannot** have `"00"` as the middle two digits
* **Cannot** end in `"0000"`
Any other valid 9-digit SSN format outside of these constraints is acceptable for testing purposes.
***
## Step 1: Collect Merchant Information
To create a business we need to collect information about the point of contact and the information about the business
itself, you can provide any combination of the following fields:
### Point of Contact Information
* First name ( point of contact for the business )
* Last name ( point of contact for the business )
* Email
* Phone
### Business Information
* Name ( legal name of the business )
* EIN ( Employer Identification Number )
* Trading Name (DBA - Doing Business As)
* Entity Type ( e.g. Sole Proprietor, LLC, etc. )
* Incorporation Date ( date when the business was incorporated )
* Incorporation State ( state where the business was incorporated )
* Address ( business address )
* Transaction Fee Percent ( default transaction fee percentage, if applicable )
* Transaction Fee Fixed ( default transaction fee, if applicable )
* Maximum Transaction Fee ( maximum transaction fee that can be charged on a transaction )
### Beneficial Owners
*At least one beneficial owner is required for compliance verification. You must provide the following fields for each owner:*
* First Name
* Last Name
* Email
* Phone
* DOB ( Date of Birth )
* SSN ( Social Security Number, last 4 or full 9 digits )
* Address
* Is Beneficial Owner ( must be true for at least one owner )
* Is Director
* Is Significant Control Person
* Ownership Percentage
### Required Fields for Compliance Verification
* Point of Contact Information ( all fields )
* Business Information
* Name
* EIN
* Trading Name
* Address
* Entity Type
* Business Owners ( at least one beneficial owner is required for Compliance Approval )
* All Fields except Ownership Percentage for each Beneficial Owner
Merchants differ from businesses and individuals in that significantly more information is required to complete
KYB (Know Your Business) verification. While not all fields are mandatory, we strongly recommend providing as much
information as possible to ensure a smooth onboarding experience and to meet regulatory and compliance requirements.
See [OpenAPI documentation](https://api.grailpay.com/api/documentation#/Users/dc8054b7f4b80775e0e4cf97eaf0f196) for the full
payload and optional parameters such as **client\_reference\_id**.
***
## Testing
When testing in GrailPay's sandbox environment, you can create a merchant as usual. To include a bank account, simply
use any random 12-digit account number along with one of our supported test routing numbers.
For more details, refer to the [Technical Overview](/docs/technical/overview#testing) guide.
***
## Step 2: Provide a Bank Account
You can onboard a merchant with or without a connected bank account. If you choose to provide one, we support two methods:
### Option A: Link a Plaid Account
If you're using Plaid for bank account linking, include the following fields:
* `bank_account.plaid.access_token`
* `bank_account.plaid.account_id`
For integration details, refer to the [OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/dc8054b7f4b80775e0e4cf97eaf0f196).
### Option B: Manual Entry
You can also onboard a person using manually provided account details:
* `account_number`
* `routing_number`
* `account_name`
* `account_type` (`checking` or `savings`)
#### Real-Time Validation (Recommended)
To reduce fraud risk and ensure funds can be successfully routed, we recommend enabling real-time bank account validation
during onboarding. This feature is powered by our [Account Intelligence](/docs/risk/bank-accounts/account-intelligence) product,
which provides intelligent validation of routing and account numbers at the time of submission.
To enable this, include an actions object in your onboarding request:
```json Success (200) theme={"system"}
{
"actions": {
"account_intelligence": {
"version": "v3",
"name_match": true
}
}
}
```
This validation step confirms that the provided routing and account numbers are both valid and correctly formatted. If
name\_match is enabled, it also checks that the account holder's name aligns with the expected identity. In addition to
these checks, the response includes a **Risk Score** and a set of decisioning insights that explain how the account was
evaluated.
To understand the full structure of the response and how to interpret the results, refer to
our [Account Intelligence](/docs/risk/bank-accounts/account-intelligence#response) documentation.
Although optional, enabling this validation is strongly recommended for all production integrations to reduce failure
rates and improve payout reliability.
***
## Step 3: Review the Response
Upon success, you'll receive a response containing:
* The `uuid` of the related to the merchant. This uuid is required for future operations such as linking bank accountsand initiating transfers.
* Echoed values such as name and address
* Status of the onboarding
* Compliance Status of the Merchant
* An account intelligence object (if validation was requested)
Full response schema is available in the [OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/dc8054b7f4b80775e0e4cf97eaf0f196).
***
## Step 4: Monitor Compliance Status
After onboarding, the Merchant's Compliance Status determines whether they can process transactions. Compliance Status
is computed in real-time based on the results of KYB (Know Your Business) verification for the business entity and
KYC (Know Your Customer) verification for each Beneficial Owner.
A Merchant's Compliance Status will be one of the following:
| Status | Description | Can Transact? |
| ------------- | ----------------------------------------------------------------------------- | ------------- |
| **Pending** | KYB and/or KYC verification is still in progress. | ❌ No |
| **Approved** | The business has passed KYB and all Beneficial Owners have passed KYC. | ✅ Yes |
| **In Review** | KYB requires additional manual review; all Beneficial Owners have passed KYC. | ❌ No |
| **Rejected** | The business failed KYB or one or more Beneficial Owners failed KYC. | ❌ No |
A Merchant is Rejected if **either** the business fails KYB **or** any Beneficial Owner fails KYC. Only one failure
is required for rejection.
You can monitor Compliance Status by retrieving the merchant record or by listening for
the [ComplianceStatusChanged](/docs/technical/webhooks/events#compliance-status-changed-event) webhook event.
For a detailed explanation of compliance requirements, verification processes, and corrective actions,
refer to the [Compliance Status Overview](/docs/resources/compliance-status-overview) guide.
***
## Try It Out
You can use the [API Explorer](https://api.grailpay.com/api/documentation#/Users/dc8054b7f4b80775e0e4cf97eaf0f196) to
test onboarding flows interactively. This tool allows you to enter a payload, submit a request, and view the response
in real time.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Onboard a Person
Source: https://docs.grailpay.com/docs/technical/users/onboard-a-person
## What is a Person?
A **Person** in the GrailPay Payments API represents an individual who can send or receive ACH transfers. Each person is
uniquely identified by a `uuid`, which is returned upon successful onboarding and is required for future operations
such as linking bank accounts and initiating transfers.
The first step in integrating with the GrailPay Payments API is to onboard a person and this guide outlines the onboarding
workflow and provides context for common use cases. For complete request and response
schemas, refer to the [Onboard a Person OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/f60626f153fa21e572de57ce1c91fc4d).
We do have an alternative onboarding method outside of our API that allows you to create a person using our BankLink
SDK product. This method allows you to embed our bank link SDK directly into your application and users can connect
their bank account using open banking. For more information, see the [BankLink SDK documentation](../bank-link-sdk/overview).
***
## Testing
When testing in GrailPay’s sandbox environment, you can create a person as usual. To include a bank account, simply
use any random 12-digit account number along with one of our supported test routing numbers.
For more details, refer to the [Technical Overview](/docs/technical/overview#testing) guide.
## Step 1: Collect Person Information
To create a person, you can provide any combination of the following fields:
* First name
* Last name
* Email
* Phone
* Address
While only a bank account is required to create a person, we **strongly recommend** supplying full information to
ensure a smooth onboarding experience and help meet regulatory and compliance standards.
See [Onboard a Person OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/f60626f153fa21e572de57ce1c91fc4d) for the full
payload and optional parameters such as **client\_reference\_id**.
***
## Step 2: Link to a Merchant for Billing Attribution
If you want to associate billable events with a merchant, include the **billing\_merchant\_user\_uuid** in your request
payload. This value should reference a user tied to a KYB-verified merchant. You will receive this UUID when you onboard
the merchant using the [Onboard a Merchant](onboard-a-merchant) endpoint.
**Field:**
`billing_merchant_user_uuid` — UUID of the merchant user that should receive billing attribution.
This enables GrailPay to accurately report on merchant usage and ensures billing traceability across your organization.
While adding a billing user uuid is not required, we **strongly recommend** supplying this information to ensure
accurate tracking of usage for your internal billing purposes.
***
## Step 3: Provide a Bank Account
You can onboard a person with or without a connected bank account. If you choose to provide one, we support two methods:
### Option A: Link a Plaid Account
If you're using Plaid for bank account linking, include the following fields:
* `bank_account.plaid.access_token`
* `bank_account.plaid.account_id`
For integration details, refer to the [Onboard a Person OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/f60626f153fa21e572de57ce1c91fc4d).
### Option B: Manual Entry
You can also onboard a person using manually provided account details:
* `account_number`
* `routing_number`
* `account_name`
* `account_type` (`checking` or `savings`)
#### Real-Time Validation (Recommended)
To reduce fraud risk and ensure funds can be successfully routed, we recommend enabling real-time bank account validation
during onboarding. This feature is powered by our [Account Intelligence](/docs/risk/bank-accounts/account-intelligence) product,
which provides intelligent validation of routing and account numbers at the time of submission.
To enable this, include an actions object in your onboarding request:
```json Success (200) theme={"system"}
{
"actions": {
"account_intelligence": {
"version": "v3",
"name_match": true
}
}
}
```
This validation step confirms that the provided routing and account numbers are both valid and correctly formatted. If
name\_match is enabled, it also checks that the account holder’s name aligns with the expected identity. In addition to
these checks, the response includes a **Risk Score** and a set of decisioning insights that explain how the account was
evaluated.
To understand the full structure of the response and how to interpret the results, refer to
our [Account Intelligence](/docs/risk/bank-accounts/account-intelligence#response) documentation.
Although optional, enabling this validation is strongly recommended for all production integrations to reduce failure
rates and improve payout reliability.
***
## Step 4: Review the Response
Upon success, you’ll receive a response containing:
* The person’s `uuid`
* Echoed values such as name and address
* Status of the onboarding
* An account intelligence object (if validation was requested)
Full response schema is available in the [Onboard a Person OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/f60626f153fa21e572de57ce1c91fc4d).
***
### Error Handling
The onboarding endpoint may return the following common HTTP status codes:
| Status Code | Description |
| ----------- | ------------------------------------------------------------------ |
| `400` | Bad Request — e.g., missing headers or malformed request |
| `401` | Unauthorized — invalid or malformed token |
| `403` | Forbidden — blocked IP address or restricted access |
| `406` | Not Acceptable — failed account validation or invalid billing UUID |
| `422` | Unprocessable Entity — field-level validation errors |
For complete error formats and examples, visit the [Onboard a Person OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Users/f60626f153fa21e572de57ce1c91fc4d).
***
## Try It Out
You can use the [API Explorer](https://api.grailpay.com/api/documentation#/Users/f60626f153fa21e572de57ce1c91fc4d) to
test onboarding flows interactively. This tool allows you to enter a payload, submit a request, and view the response
in real time.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Webhook Events
Source: https://docs.grailpay.com/docs/technical/webhooks/events
## Overview
Webhooks allow your application to react in real-time to important events across the GrailPay platform—without the need
to constantly poll the API. Whenever a subscribed event occurs (like a payout being sent or a transaction status changing),
GrailPay sends a secure HTTP POST request directly to your server with the event details.
This push-based architecture enables you to automate workflows, synchronize data, and trigger time-sensitive actions the
moment something happens.
Each webhook is sent to a configured HTTPS endpoint, and your server is expected to return an HTTP 200 OK response to
confirm receipt. Webhooks may be sent to both vendors and processors, and the payloads are tailored to each recipient’s
role and financing type (e.g., credit, debit, or alternate flows) to ensure accurate processing and traceability.
All webhook payloads are shown in JSON format and from the perspective of a vendor receiving an event. If a processor
is receiving the event, the payload will be the same, but it will include the `vendor_uuid` field to identify which
vendor the event is related to. This is true for all webhook events unless the event denotes a Processor only event.
### Authenticating Webhooks
To verify that a webhook really came from GrailPay and not a malicious actor, every webhook request includes a special
signature header: `X-Caller-Auth`.
This header contains a `SHA-256` hash of your API key, and it should be validated in your server-side code to ensure the
authenticity of the request.
We strongly recommend implementing this verification step. It helps protect your application from spoofed requests and
ensures that only genuine GrailPay events are processed.
### Security
GrailPay requires all webhook endpoints to use HTTPS to guarantee the secure delivery of sensitive financial event data.
This applies across both sandbox and production environments—non-secure HTTP URLs will be rejected, and no webhook events
will be delivered until a valid HTTPS endpoint is configured.
Requiring HTTPS helps protect against man-in-the-middle attacks and ensures compliance with industry-standard security
practices. Before subscribing to webhook events, make sure your server is accessible via a secure, publicly reachable
HTTPS URL with a valid SSL certificate.
If your environment isn’t properly configured, webhook notifications will silently fail to deliver.
### Response & Retry Logic
To acknowledge receipt of a webhook event, your server must respond with an HTTP 200 OK status code. This tells GrailPay
that the event was received and processed successfully.
If a 200 OK response is not returned, we consider the delivery attempt a failure. In such cases, GrailPay will
automatically retry sending the webhook at increasing intervals for a limited number of attempts. Repeated failures
may result in delayed event delivery or eventual webhook deactivation.
To ensure reliable communication, your webhook handler should be:
* Idempotent – able to handle duplicate event deliveries without side effects.
* Efficient – respond quickly, even if additional processing is handled asynchronously.
Properly handling responses and retries ensures smooth, real-time integration with your systems.
***
## Available Webhook Events
Our webhooks are not specific to any particular entity in our Payments Application. So for
example, you would not want to register an individual webhook for every merchant that you onboard. Each of those
webhooks urls will get the same data each time an event is triggered. The approach we recommend is to register a
single webhook url to the events you wish to subscribe to. Then you can use the event data to determine which
entity the event is related to.
***
### Bank Account Events
Bank accounts are foundational to ACH transactions within the GrailPay platform, serving as the endpoints for debits
and credits between payors and payees. Whether added through direct input, or our Bank Link SDK, these accounts must be
tracked and maintained throughout their lifecycle. GrailPay provides webhook events that notify you when a bank account
has been successfully linked, failed to link, or has been deleted. These events allow your system to react to key
account changes in real time—helping to keep records accurate and ensuring smooth payment processing.
By subscribing to bank account-related webhooks, your integration gains full visibility into the account lifecycle. For
example, a failed bank link may prompt you to notify a user, while a successful link may trigger onboarding flows or
transaction enablement. These webhooks are essential for keeping your application responsive, user-friendly, and
compliant with modern financial workflows.
#### Bank Account Removed Event
This webhook event is triggered whenever a bank account is deleted from the GrailPay system. It allows your integration
to stay in sync with the current state of user-linked financial accounts, ensuring that your records remain accurate and
up-to-date.
When this event occurs, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "BankAccountRemoved",
"account_uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"user_uuid": "ce334276-3a2e-4db6-8e67-600ed1fcd397",
"aggregator_type": "bank_link",
"account_number": "45287159",
"provider_name": "chase",
"routing_number": "011401533"
}
```
#### Bank Link Failed Event
This webhook event is triggered whenever a bank account fails to link successfully using GrailPay’s [Bank Link SDK](/docs/technical/bank-link/overview).
This allows your application to track and respond to failed bank connection attempts—whether due to user cancellation,
timeouts, or external provider errors.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "BankLinkFailed",
"user_uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"aggregator_type": "bank_link",
"vendor_id": "123456",
"failed_reason": ".....",
"source": "api",
"ui_notified": true|false
}
```
#### Bank Linked Successfully Event
This event is triggered when a bank account is successfully linked using GrailPay’s [Bank Link SDK](/docs/technical/bank-link/overview).
flow. It confirms that the user has completed the linking process and the account is now available for transactions or
balance checks.
You can use this event to update your internal records, unlock funding workflows, or display confirmation messages to the
user.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "BankLinkedSuccessfully",
"account_uuid": "becdf333-53cc-4db1-a6bd-a01b1ba9585c",
"user_uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"aggregator_type": "bank_link",
"source": "api",
"account_id": "acc_9Dw8QmdZtcXrEMDuZWLyfi",
"account_number": "45287159",
"routing_number": "011401533",
"provider_name": "chase",
"vendor_id": "12345",
"ui_notified": true|false
}
```
***
### Batch Payout Events
Batch payouts are a key mechanism in the GrailPay Payments platform, allowing multiple transactions to be grouped together
and disbursed to a single entity’s linked bank account in one consolidated action. This not only streamlines the
movement of funds but also simplifies reconciliation and reporting for partners managing high transaction volumes.
GrailPay emits batch payout webhook events for four entity types: Business, Merchant, Person, and Processor. These
events provide visibility into when a payout batch is created and initiated, enabling partners to track disbursement
activity and trigger downstream processes like ledger updates or partner notifications. Each event includes a structured
payload specific to the entity type, which is outlined in the sections below.
#### Batch Payout Business Event
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "BatchPayoutBusiness",
"uuid": "9bd53fe1-ac74-4e81-b6f2-be88b7c86a68",
"amount": 10100,
"bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": null
},
"created_at": "2024-04-18 01:00:20",
"updated_at": "2024-04-18 01:00:20"
}
```
#### Batch Payout Merchant Event
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "BatchPayoutMerchant",
"uuid": "9bd53fe1-ac74-4e81-b6f2-be88b7c86a68",
"amount": 10100,
"bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": null
},
"created_at": "2024-04-18 01:00:20",
"updated_at": "2024-04-18 01:00:20"
}
```
#### Batch Payout Person Event
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "BatchPayoutPerson",
"uuid": "9bd53fe1-ac74-4e81-b6f2-be88b7c86a68",
"amount": 10100,
"bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": null
},
"created_at": "2024-04-18 01:00:20",
"updated_at": "2024-04-18 01:00:20"
}
```
#### Batch Payout Processor Event
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event ( Processor Only ) theme={"system"}
{
"event": "BatchPayout",
"uuid": "9bd53fe1-ac74-4e81-b6f2-be88b7c86a68",
"amount": 10100,
"trace_id": "ach_11jzggrptrftyd",
"modality": {
"payment_rail": "ach",
"speed": "standard"
},
"bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": null
},
"created_at": "2024-04-18 01:00:20",
"updated_at": "2024-04-18 01:00:20"
}
```
***
### Batch Refund Event
This webhook event is triggered when a debit ACH is initiated from the processor’s bank account to fund a batch of
refunds. It signifies that GrailPay has begun collecting funds to fulfill refund requests that were previously grouped
and approved for processing.
This event is typically used in integrations where the processor, rather than the original payee, is responsible for
funding the refund payouts. It ensures visibility into the moment refund funding is initiated, allowing for downstream
reconciliation or tracking.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event ( Processor Only ) theme={"system"}
{
"event": "BatchRefund",
"uuid": "9bd53fe1-ac74-4e81-b6f2-be88b7c86a68",
"amount": 10100,
"trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"debit_bank_id": "ach_11n4zydc1twjvqg",
"debit_trace_id": null
},
"created_at": "2024-04-18 01:00:20",
"updated_at": "2024-04-18 01:00:20"
}
```
***
### Business Events
GrailPay emits webhook events when key lifecycle changes occur for business entities within the platform.
These events notify you in real time when a business is created or updated, allowing you to synchronize
entity data across your systems, trigger onboarding workflows, or audit compliance checkpoints as part of your Payments
integration.
#### Business Created Event
This webhook is triggered whenever a new Business entity is successfully created within the GrailPay system.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "BusinessCreated",
"uuid": "401c79dd-d38c-4c3a-8edf-1afac5914d2d",
"first_name": "Jack",
"last_name": "Jones",
"name": "Jack Inc.",
"tin": "961862955",
"user_status": "Approved|On Hold|Rejected",
"kyb_status": "Approved|In Review|Failed|null",
"email": "[email protected]",
"phone": "2541234567",
"address": {
"street_address": "20 Elmora Ave",
"additional_address": "",
"city": "Elizabeth",
"state": "NJ",
"country": "US",
"zip": "07202"
},
"business_owners": [],
"financing_credit_balance": null,
"source": "api",
"created_at": "2023-06-10 14:15:54",
"updated_at": "2023-06-10 14:15:54"
}
```
#### Business Updated Event
This webhook is triggered whenever a business entity is updated within the GrailPay system.
Updates may include changes to KYB information, business name, address, or other identifying details.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "BusinessUpdated",
"uuid": "401c79dd-d38c-4c3a-8edf-1afac5914d2d",
"first_name": "Jack",
"last_name": "Jones",
"name": "Jack Inc.",
"tin": "961862955",
"user_status": "APPROVED|ON_HOLD|REJECTED|SCHEDULED_FOR_DELETE",
"kyb_status": "in_review|approved|failed|null",
"kyb_rejected_reason": "Reason for KYB rejection ( ie. Cannot match TIN )",
"email": "[email protected]",
"phone": "2541234567",
"address": {
"street_address": "20 Elmora Ave",
"additional_address": "",
"city": "Elizabeth",
"state": "NJ",
"country": "US",
"zip": "07202"
},
"business_owners": [],
"financing_credit_balance": null,
"source": "api",
"created_at": "2023-06-10 14:15:54",
"updated_at": "2023-06-10 14:15:54"
}
```
***
### Clawback Events
Clawbacks occur when a transaction that has already been disbursed to the payee fails—often due to insufficient funds,
a return from the receiving bank, or a rejection by the ACH network. In such cases, GrailPay initiates a payout return
to recover the funds, which is referred to as a clawback. These events are crucial for maintaining the integrity of your
payment workflows, ensuring that failed disbursements are properly tracked and reconciled.
GrailPay emits a series of webhook events throughout the clawback lifecycle to notify you when a return has been
initiated (ClawbackStarted), fails to complete (ClawbackFailed), or finishes successfully (ClawbackCompleted). By
subscribing to these events, you can monitor failed transactions in real time, update internal records, trigger customer
notifications, or pause related operations until resolution.
#### Clawback Capture Started Event
This webhook is triggered when a payout return ( clawback) capture has been initiated by GrailPay as part of a refund
or recovery process.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "ClawbackCaptureStarted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "CLAWBACK_ACH_PENDING",
"capture_status": "CAPTURE_ACH_FAILED",
"payout_status": "PAYOUT_COMPLETE",
"clawback_status": "CLAWBACK_ACH_PENDING",
"clawback_trace_id": "ach_11jzggrptrftyd",
"reverse_payout_status": null,
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"type": "api",
"failure_reason": "Insufficient funds",
"failure_code": "R01",
"clawback_bank_identifiers": {
"debit_bank_id": "ach_11n4zydc1twjvqg",
"debit_trace_id": null
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Clawback Completed Event
This webhook is triggered when a payout return (clawback) has been initiated by GrailPay as part of a completed refund
or recovery process.
This event confirms that GrailPay has initiated the return of funds. It does not guarantee that the receiving bank
has successfully delivered the funds.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "ClawbackCompleted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "CLAWBACK_COMPLETE",
"capture_status": "CAPTURE_ACH_FAILED",
"payout_status": "PAYOUT_COMPLETE",
"clawback_status": "CLAWBACK_COMPLETE",
"clawback_trace_id": "ach_11jzggrptrftyd",
"reverse_payout_status": null,
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"type": "api",
"failure_reason": "Insufficient funds",
"failure_code": "R01",
"clawback_bank_identifiers": {
"debit_bank_id": "ach_11n4zydc1twjvqg",
"debit_trace_id": "2671067643716908"
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Clawback Failed Event
This webhook is triggered when a payout return (clawback) attempt fails within the GrailPay system.
A failed clawback typically indicates that the funds could not be withdrawn from the recipient’s bank account due
to reasons such as insufficient funds or a closed account.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "ClawbackFailed",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "CLAWBACK_ACH_FAILED",
"capture_status": "CAPTURE_ACH_FAILED",
"payout_status": "PAYOUT_COMPLETE",
"clawback_status": "CLAWBACK_ACH_FAILED",
"clawback_trace_id": "ach_11jzggrptrftyd",
"reverse_payout_status": null,
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"type": "api",
"failure_reason": "Insufficient funds",
"failure_code": "R01",
"clawback_bank_identifiers": {
"debit_bank_id": "ach_11n4zydc1twjvqg",
"debit_trace_id": "2671067643716908"
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Clawback Started Event
This webhook is triggered when a payout return (clawback) has been initiated by the GrailPay system, but the ACH debit
has not been created. A clawback is required when a transaction fails (e.g., due to an ACH return) after the payout
(credit) has already been disbursed to the recipient.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "ClawbackStarted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "CLAWBACK_PENDING",
"capture_status": "CAPTURE_ACH_FAILED",
"payout_status": "PAYOUT_COMPLETE",
"clawback_status": "CLAWBACK_PENDING",
"clawback_trace_id": null,
"reverse_payout_status": null,
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"type": "api",
"failure_reason": null,
"failure_code": null,
"clawback_bank_identifiers": null,
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
***
### Compliance Status Events
GrailPay provides webhook events to notify you when the compliance status of a merchant entity changes. These events
are crucial for maintaining up-to-date records and ensuring that your operations align with regulatory requirements. By
subscribing to these events, you can automate workflows that respond to compliance status updates, such as pausing
transactions, triggering additional verification steps, or notifying relevant stakeholders.
#### Compliance Status Changed Event
This webhook event is triggered whenever a merchant's compliance status is changed within the GrailPay platform.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "ComplianceStatusChanged",
"entity": {
"type": "merchant",
"uuid": "8f5a6e9d-2c41-4f90-bb3f-9e8a9a3b7f1c"
},
"compliance": {
"status": "rejected",
"kyb": {
"status": "approved",
"message": null,
"updated_at": "2025-12-11 13:59:21"
},
"kyc": [
{
"uuid": "7f4e8a02-3d5a-4c3b-b823-d8c3125b3f8d",
"first_name": "John",
"last_name": "Doe",
"status": "approved",
"message": null,
"updated_at": "2025-12-11 13:59:21"
},
{
"uuid": "9e2a7cb7-34c1-4c52-a0df-4f9f5704e6e2",
"first_name": "Jane",
"last_name": "Doe",
"status": "rejected",
"message": "Invalid Address",
"updated_at": "2025-12-11 13:59:21"
}
]
}
}
```
***
### Merchant Events
GrailPay emits dedicated webhook events when a merchant entity is created or updated on the platform. These events provide
real-time notifications that allow you to synchronize merchant records, trigger onboarding or verification flows, and
monitor compliance status.
Merchants in GrailPay are a distinct type of business that must complete Know Your Business (KYB) verification. While
merchants were previously tracked using shared business webhooks, we now offer merchant-specific events—such as
**MerchantCreated** and **MerchantUpdated** to give platforms finer control and visibility into merchant lifecycle changes.
Each payload includes detailed metadata, enabling accurate reconciliation of merchant status across your systems.
#### Merchant Created Event
Our Payments API emits a MerchantCreated webhook event each time a new merchant has been successfully onboarded and registered
within the GrailPay platform.
For more information about KYB verification and requirements, refer to the [KYB Overview](/docs/resources/kyb-overview) guide.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "MerchantCreated",
"merchant": {
"uuid": "b8f1e132-3bfc-4e23-89f7-9e541e8ffb33",
"business_type": "merchant",
"kyb_status": "pending|approved|in_review|rejected",
"kyb_rejected_reason": "Reason for KYB rejection ( ie. Cannot match TIN )",
"name": "Acme Incorporation",
"tin": "123456789",
"trading_name": "Acme Inc",
"entity_type": "LLC",
"incorporation_date": "2024-02-02",
"incorporation_state": "CO",
"address_type": "Registered",
"address": {
"line_1": "123 Main St",
"line_2": "Apt 4B",
"city": "Denver",
"state": "CO",
"zip": "80203"
},
"transaction_fee_percent": 2.5,
"transaction_fee_fixed": 100,
"maximum_transaction_fee": 5000,
"payout_type": "individual",
"client_reference_id": "bus_123456789",
"timestamps": {
"created_at": "2025-06-01 13:57:03",
"updated_at": "2025-06-01 13:57:09"
}
},
"beneficial_owners": [
{
"first_name": "Jane",
"last_name": "Doe",
"dob": "2023-04-11",
"ssn9": "123456789",
"address": {
"line_1": "123 Main St",
"line_2": "Apt 4B",
"city": "Denver",
"state": "CO",
"zip": "80203"
},
"is_beneficial_owner": true,
"is_director": false,
"is_significant_control_person": false,
"ownership_percentage": 25,
"email": "owner@test.com",
"phone": "2457856490",
"occupation": "Co-founder",
"timestamps": {
"created_at": "2025-06-01 13:57:03",
"updated_at": "2025-06-01 13:57:09"
}
}
],
"relations": {
"person": { "uuid": "e7b8f1e1-3bfc-4e23-89f7-9e541e8ffb33" }
}
}
```
#### Merchant Updated Event
Our Payments API emits a MerchantUpdated webhook event whenever a merchant’s details have been updated within the GrailPay
platform. This may include changes to business name, address, or other KYB-related fields.
For more information about KYB verification and requirements, refer to the [KYB Overview](/docs/resources/kyb-overview) guide.
This webhook should be actively monitored to detect changes in the merchant’s KYB status. Updates to KYB status may
impact the merchant’s ability to send or receive funds and should trigger appropriate compliance or onboarding
workflows on your end.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "MerchantUpdated",
"merchant": {
"uuid": "b8f1e132-3bfc-4e23-89f7-9e541e8ffb33",
"business_type": "merchant",
"kyb_status": "pending|approved|in_review|rejected",
"kyb_rejected_reason": "Reason for KYB rejection ( ie. Cannot match TIN )",
"name": "Acme Incorporation",
"tin": "123456789",
"trading_name": "Acme Inc",
"entity_type": "LLC",
"incorporation_date": "2024-02-02",
"incorporation_state": "CO",
"address_type": "Registered",
"address": {
"line_1": "123 Main St",
"line_2": "Apt 4B",
"city": "Denver",
"state": "CO",
"zip": "80203"
},
"transaction_fee_percent": 2.5,
"transaction_fee_fixed": 100,
"maximum_transaction_fee": 5000,
"payout_type": "individual",
"client_reference_id": "bus_123456789",
"timestamps": {
"created_at": "2025-06-01 13:57:03",
"updated_at": "2025-06-01 13:57:09"
}
},
"beneficial_owners": [
{
"first_name": "Jane",
"last_name": "Doe",
"dob": "2023-04-11",
"ssn9": "123456789",
"address": {
"line_1": "123 Main St",
"line_2": "Apt 4B",
"city": "Denver",
"state": "CO",
"zip": "80203"
},
"is_beneficial_owner": true,
"is_director": false,
"is_significant_control_person": false,
"ownership_percentage": 25,
"email": "owner@test.com",
"phone": "2457856490",
"occupation": "Co-founder",
"timestamps": {
"created_at": "2025-06-01 13:57:03",
"updated_at": "2025-06-01 13:57:09"
}
}
],
"relations": {
"person": { "uuid": "e7b8f1e1-3bfc-4e23-89f7-9e541e8ffb33" }
}
}
```
***
### NOC Events
NOC (Notice of Change) events are critical notifications within the GrailPay platform that inform you when an ACH
transaction has encountered an issue requiring attention. NOCs are issued by banks or the ACH network to indicate
problems such as incorrect account information, closed accounts, or other discrepancies that need to be resolved.
By subscribing to NOC webhook events, your integration can receive real-time alerts whenever a NOC is received and
processed by GrailPay, which we are required by Nacha compliance to do within six days of receiving them. Our system
handles these account detail updates automatically, but if we encounter an issue, we will contact you to work together
to make the change. For this reason, it is important to subscribe to the NOC webhooks to know when we've received and
then processed NOCs.
This allows you to take prompt action—such as updating account details, notifying users, or initiating corrective
measures—to ensure smooth payment processing and maintain compliance with ACH regulations.
#### NOC Received Event
This webhook is triggered whenever a Notice of Change (NOC) has been received by our system.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "NocReceived",
"uuid": "98cd3aa4-9c46-439f-89b0-14c4f14bb628",
"user_uuid": "550e8400-e29b-41d4-a716-446655440000",
"bank_account_uuid": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"provider_type": "manual",
"account_number": "1234567890",
"routing_number": "021000021",
"account_name": "John Doe",
"account_type": "checking",
"old_account_number": "9876543210",
"old_routing_number": "987654321",
"old_account_name": "John Doe",
"old_account_type": "savings",
"vendor_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"received_at": "2026-04-05 14:25:33"
}
```
#### NOC Processed Event
This webhook is triggered whenever a Notice of Change (NOC) has been processed within the
GrailPay system.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "NocProcessed",
"uuid": "98cd3aa4-9c46-439f-89b0-14c4f14bb628",
"user_uuid": "550e8400-e29b-41d4-a716-446655440000",
"bank_account_uuid": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"provider_type": "manual",
"account_number": "1234567890",
"routing_number": "021000021",
"account_name": "John Doe",
"account_type": "checking",
"old_account_number": "9876543210",
"old_routing_number": "987654321",
"old_account_name": "John Doe",
"old_account_type": "savings",
"vendor_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"received_at": "2026-04-05 14:25:33",
"processed_at": "2026-04-05 15:25:33"
}
```
***
### Payout Events
Payout events reflect the final stage in GrailPay’s transaction lifecycle, where funds are disbursed from the platform
to a payee’s linked bank account. These events are essential for tracking when money has been successfully moved out of
the system—or when something has gone wrong. Whether you’re monitoring cash flow, updating transaction statuses, or
reconciling financial data, payout events provide the real-time signals you need.
GrailPay emits webhook events for each critical state in a payout’s lifecycle: when a payout is initiated (PayoutPending),
fails (PayoutFailed), or completes (PayoutCompleted). These events help ensure transparency and operational continuity
across your systems. They allow your application to respond dynamically—such as notifying users, retrying failed
disbursements, or logging final confirmations for reporting and audit purposes.
#### Payout Completed Event
This webhook is triggered each time a payout (transfer of funds to the payee) has been completed.
Please note that this event indicates the payout has been initiated by GrailPay. It does not confirm final delivery
of funds by the receiving bank.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "PayoutCompleted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "PAYOUT_COMPLETE",
"capture_status": "CAPTURE_COMPLETE",
"payout_status": "PAYOUT_COMPLETE",
"reverse_payout_status": null,
"clawback_status": null,
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"modality": {
"payment_rail": "ach",
"speed": "standard"
},
"payout_bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": "2789745764564645"
},
"type": "api",
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Payout Created Event
This webhook is triggered each time a payout (transfer of funds to the payee) has been created and is pending.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "PayoutCreated",
"payout": {
"uuid": "8b2536c0-01c4-4699-8a97-b68eb8fb6b4f",
"client_reference_id": "Payout_1784_1710950428",
"type": "individual",
"status": "PAYOUT_ACH_PENDING",
"trace_id": "ach_11jzp20wtve6z8",
"amount": 3245,
"speed": "standard",
"ach_return_code": null,
"modality": {
"payment_rail": "ach",
"speed": "standard"
},
"bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": null
},
"payee": {
"uuid": "1a437e1f-c112-4534-ac44-2de71cec9f1f",
"user_uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"type": "business",
"name": "Acme Pest Control",
"processor_mid": null,
"bank_account": {
"aggregator_type": "manual",
"uuid": "9b97f121-a449-4b52-9f36-6c55f18394d6"
}
},
"timestamps": {
"created_at": "2024-06-25 13:57:03",
"updated_at": "2024-06-25 13:57:09"
},
"ach_timestamps": {
"created_at": "2024-03-20 16:00:28",
"processed_at": null,
"sent_at": null,
"settled_at": "2024-03-20 17:00:27",
"failed_at": null,
"cancelled_at": null
}
}
}
```
#### Payout Failed Event
This webhook is triggered when a payout—the transfer of funds to the payee’s bank account—fails. This may occur for
several reasons, such as invalid account details, routing issues, or problems reported by the receiving financial
institution.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "PayoutFailed",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "PAYOUT_ACH_FAILED",
"capture_status": "CAPTURE_COMPLETE",
"payout_status": "PAYOUT_ACH_FAILED",
"clawback_status": null,
"reverse_payout_status": "REVERSE_PAYOUT_PENDING",
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"type": "api",
"modality": {
"payment_rail": "ach",
"speed": "standard"
},
"payout_bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": "2789745764564645"
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
***
### Person Events
GrailPay emits webhook events when key lifecycle changes occur for individual users—referred to as People in the
platform. These events notify you in real time when a person is created or updated, enabling you to synchronize user
records across your systems, trigger onboarding processes, or verify changes in identity-related data.
While people may be linked to a business or merchant, they can also exist independently as participants in ACH
transactions. Each webhook payload includes unique identifiers and metadata to help you track a person’s lifecycle
within your platform and respond appropriately to changes in their profile.
#### Person Created Event
Our Payments API emits a PersonCreated webhook whenever a new individual user is added to the platform—either as part of
onboarding or through a standalone user creation flow.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "PersonCreated",
"person": {
"client_reference_id": "person_98345",
"uuid": "7c41f6a2-a4b9-4df8-9225-2c1b7312042e",
"first_name": "Jane",
"last_name": "Doe",
"email": "test@grailpay.com",
"phone": "1234567890",
"address": {
"line_1": "123 Main St",
"line_2": "Apt 4B",
"city": "Denver",
"state": "CO",
"zip": "80203"
},
"payout_type": "individual",
"timestamps": {
"created_at": "2025-06-01 13:57:03",
"updated_at": "2025-06-01 13:57:09"
}
}
}
```
#### Person Status Changed Event
This webhook is triggered whenever a person's status is updated within the GrailPay platform due to risk, compliance,
or business rules. Status changes may include transitions such as `ON_HOLD_FRAUD_PAYEE`, `ON_HOLD_SANCTIONS`, or other
states that restrict the person's ability to transact.
These updates typically occur when GrailPay identifies a risk-related reason to suspend or restrict activity for an
individual user. This webhook allows your system to immediately respond—by disabling UI elements, blocking actions, or
initiating internal support workflows.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "PersonStatusChanged",
"person": {
"uuid": "7c41f6a2-a4b9-4df8-9225-2c1b7312042e",
"status": "ON_HOLD_FRAUD_PAYEE"
},
"change_reason": "fraud_related_return",
"outcome": "This person and its associated entities will no longer be able to transact within our payments api.",
"action": "Contact support@grailpay.com to resolve the issue.",
"relations": {
"merchant": {
"uuid": "fae2de1a-68c9-43a0-9c63-4daf8d16a709"
}
}
}
```
#### Person Updated Event
The PersonUpdated webhook is emitted whenever an existing user’s information is modified in the GrailPay platform. This
includes changes to core fields such as name, email, phone number, or address.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "PersonUpdated",
"person": {
"client_reference_id": "person_98345",
"uuid": "7c41f6a2-a4b9-4df8-9225-2c1b7312042e",
"first_name": "Jane",
"last_name": "Doe",
"email": "test@grailpay.com",
"phone": "1234567890",
"address": {
"line_1": "123 Main St",
"line_2": "Apt 4B",
"city": "Denver",
"state": "CO",
"zip": "80203"
},
"payout_type": "individual",
"timestamps": {
"created_at": "2025-06-01 13:57:03",
"updated_at": "2025-06-01 13:57:09"
}
}
}
```
***
### Processor Payout Events
Processor payout events are triggered throughout the lifecycle of payouts initiated on behalf of a specific processor.
Unlike standard payouts, processor payouts are always processed in batches, allowing multiple payouts to be grouped and
executed together.
GrailPay emits webhook events for each key milestone in the processor payout process: when a payout is created
(ProcessorPayoutCreated), and when it completes successfully or encounters a failure (ProcessorPayoutCompleted,
ProcessorPayoutFailed). These events enable your platform to monitor payout progress, reconcile processor-level
transactions, and respond promptly to any issues that arise during processing.
#### Processor Payout Created Event
This webhook is triggered when a processor payout is created.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "ProcessorPayoutCreated",
"payout": {
"uuid": "8b2536c0-01c4-4699-8a97-b68eb8fb6b4f",
"type": "batch",
"status": "PENDING",
"amount": 5278900,
"ach_return_code": null,
"ledger_shared": false,
"trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": null
},
"modality": {
"payment_rail": "ach",
"speed": "standard"
},
"timestamps": {
"created_at": "2024-06-25 13:57:03",
"updated_at": "2024-06-25 13:57:09"
},
"ach_timestamps": {
"created_at": "2024-03-20 16:00:28",
"processed_at": null,
"sent_at": null,
"settled_at": null,
"failed_at": null,
}
},
"relations": {
"vendor": { "uuid" : "fae2de1a-68c9-43a0-9c63-4daf8d16a709" }
}
}
```
#### Processor Payout Completed Event
This webhook is triggered when a processor payout is completed.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "ProcessorPayoutCompleted",
"payout": {
"uuid": "8b2536c0-01c4-4699-8a97-b68eb8fb6b4f",
"type": "batch",
"status": "PENDING",
"amount": 5278900,
"ach_return_code": null,
"ledger_shared": false,
"trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": "2789745764564645"
},
"modality": {
"payment_rail": "ach",
"speed": "standard"
},
"timestamps": {
"created_at": "2024-06-25 13:57:03",
"updated_at": "2024-06-25 13:57:09"
},
"ach_timestamps": {
"created_at": "2024-03-20 16:00:28",
"processed_at": "2024-03-21 16:00:28",
"sent_at": "2024-03-21 16:00:28",
"settled_at": "2024-03-21 16:00:28",
"failed_at": null,
}
},
"relations": {
"vendor": { "uuid" : "fae2de1a-68c9-43a0-9c63-4daf8d16a709" }
}
}
```
#### Processor Payout Failed Event
This webhook is triggered when a processor payout fails.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "ProcessorPayoutFailed",
"payout": {
"uuid": "8b2536c0-01c4-4699-8a97-b68eb8fb6b4f",
"type": "batch",
"status": "PENDING",
"amount": 5278900,
"ach_return_code": null,
"ledger_shared": false,
"trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": "2789745764564645"
},
"modality": {
"payment_rail": "ach",
"speed": "standard"
},
"timestamps": {
"created_at": "2024-06-25 13:57:03",
"updated_at": "2024-06-25 13:57:09"
},
"ach_timestamps": {
"created_at": "2024-03-20 16:00:28",
"processed_at": null,
"sent_at": null,
"settled_at": null,
"failed_at": "2024-03-21 16:00:28"
}
},
"relations": {
"vendor": { "uuid" : "fae2de1a-68c9-43a0-9c63-4daf8d16a709" }
}
}
```
***
### Refund Events
Refund events are triggered throughout the lifecycle of a refund transaction—starting from the moment a refund is
created, through the capture of funds from the payee or processor, and concluding with the delivery of funds back to the
original payer. These events help ensure your platform can track, audit, and respond to refunds in real time.
GrailPay emits webhook events for each key milestone in the refund process: when a refund is initiated (RefundPending),
when the corresponding debit ACH is started, completed, or fails (RefundCaptureStarted, RefundCaptureCompleted, RefundCaptureFailed),
and when the outbound credit to the payer is initiated, completed, or fails (RefundPayoutPending, RefundPayoutCompleted, RefundPayoutFailed).
Together, these events provide a complete picture of refund flow—enabling you to keep users informed, automate
reconciliation, and ensure accurate financial reporting.
#### Refund Cancelled Event
This webhook is triggered when a refund has been cancelled.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "RefundCancelled",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"transaction_uuid": "7049f46f-8502-402e-952b-b29ad5ec8a55",
"amount": 12000,
"capture_delay_days": 1,
"payout_delay_days": 2,
"status": "REFUND_CANCELLED",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"cancel_reason": "The transaction has failed",
"client_reference_id": "",
"capture": {
"bank_identifiers": null
},
"payout": {
"bank_identifiers": null
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Refund Capture Completed Event
This webhook is triggered when a debit ACH transaction has been successfully completed from either the payee’s or
processor’s bank account to fund a refund.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "RefundCaptureCompleted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"transaction_uuid": "7049f46f-8502-402e-952b-b29ad5ec8a55",
"capture_delay_days": 1,
"payout_delay_days": 2,
"amount": 12000,
"status": "REFUND_ACH_PENDING",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"client_reference_id": "",
"capture": {
"bank_identifiers": {
"debit_bank_id": "ach_11n4zydc1twjvqg",
"debit_trace_id": "2671067643716908"
},
},
"payout": {
"bank_identifiers": null
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Refund Capture Failed Event
This webhook is triggered when a debit ACH fails while attempting to collect funds for a refund from either the payee’s
or processor’s bank account.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "RefundCaptureFailed",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"transaction_uuid": "7049f46f-8502-402e-952b-b29ad5ec8a55",
"amount": 12000,
"capture_delay_days": 1,
"payout_delay_days": 2,
"status": "REFUND_FAILED",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"client_reference_id": "",
"failure_reason": "No account or unable to locate account",
"failure_code": "R03",
"capture": {
"bank_identifiers": {
"debit_bank_id": "ach_11n4zydc1twjvqg",
"debit_trace_id": "2671067643716908"
},
},
"payout": {
"bank_identifiers": null
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Refund Capture Started Event
This webhook is triggered each time a refund request has been initiated and a debit ACH has been created from either the
payee’s or processor’s bank account.
* For individual payouts, a debit ACH is created for each refund request from the payee or processor.
* For batch payouts, a single debit ACH is created from the processor’s account to cover all refund requests for the day.
* In both cases, this webhook will be triggered for each individual refund request.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "RefundCaptureStarted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"transaction_uuid": "7049f46f-8502-402e-952b-b29ad5ec8a55",
"amount": 12000,
"capture_delay_days": 1,
"payout_delay_days": 2,
"status": "REFUND_CAPTURE_ACH_PENDING",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"client_reference_id": "",
"capture": {
"bank_identifiers": {
"debit_bank_id": "ach_11n4zydc1twjvqg",
"debit_trace_id": null
},
},
"payout": {
"bank_identifiers": null
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Refund Payout Completed Event
This webhook is triggered each time a refund has been completed and the funds have been sent to the payer’s bank account.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "RefundPayoutCompleted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"transaction_uuid": "7049f46f-8502-402e-952b-b29ad5ec8a55",
"amount": 12000,
"capture_delay_days": 1,
"payout_delay_days": 2,
"status": "REFUND_COMPLETE",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"client_reference_id": "",
"capture": {
"bank_identifiers": {
"debit_bank_id": "ach_11n4zydc1twjvqg",
"debit_trace_id": "2671067643716908"
},
},
"payout": {
"bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": "2789745764564645"
}
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Refund Payout Failed Event
This webhook is triggered when a credit ACH transfer to the payer’s bank account has failed during the refund process.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "RefundPayoutFailed",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"transaction_uuid": "7049f46f-8502-402e-952b-b29ad5ec8a55",
"amount": 12000,
"capture_delay_days": 1,
"payout_delay_days": 2,
"status": "REFUND_FAILED",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"client_reference_id": "",
"failure_reason": "No account or unable to locate account",
"failure_code": "R03",
"capture": {
"bank_identifiers": {
"debit_bank_id": "ach_11n4zydc1twjvqg",
"debit_trace_id": "2671067643716908"
},
},
"payout": {
"bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": "2789745764564645"
}
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Refund Payout Pending Event
This webhook is triggered when a credit ACH transfer has been initiated to the payer’s bank account as part of a refund.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "RefundPayoutPending",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"transaction_uuid": "7049f46f-8502-402e-952b-b29ad5ec8a55",
"amount": 12000,
"capture_delay_days": 1,
"payout_delay_days": 2,
"status": "REFUND_ACH_PENDING",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"client_reference_id": "",
"capture": {
"bank_identifiers": {
"debit_bank_id": "ach_11n4zydc1twjvqg",
"debit_trace_id": "2671067643716908"
},
},
"payout": {
"bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": null
}
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Refund Pending Event
This webhook is triggered when a refund request is first created in the system.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "RefundPending",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"transaction_uuid": "7049f46f-8502-402e-952b-b29ad5ec8a55",
"amount": 12000,
"capture_delay_days": 1,
"payout_delay_days": 2,
"status": "REFUND_PENDING",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"client_reference_id": "",
"capture": {
"bank_identifiers": null
},
"payout": {
"bank_identifiers": null
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
***
### Reverse Payout Events
Reverse payout events are emitted when GrailPay must return funds to the original payer because a payout to the intended
payee could not be completed. This can happen due to reasons like an invalid or closed bank account, or a failed delivery
attempt after the payout was initiated.
These events help your platform stay aligned with the refund and reconciliation flow for failed disbursements. GrailPay
provides webhook events for when a reverse payout is initiated (ReversePayoutStarted), successfully completed
(ReversePayoutCompleted), or fails during processing (ReversePayoutFailed). These allow you to track the full lifecycle
of the reversal process, update transaction statuses, and notify relevant users or systems of the outcome.
#### Reverse Payout Completed Event
This webhook is triggered when a reverse payout has been successfully completed.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "ReversePayoutCompleted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "PAYOUT_ACH_FAILED",
"capture_status": "CAPTURE_COMPLETE",
"payout_status": "PAYOUT_ACH_FAILED",
"clawback_status": null,
"reverse_payout_status": "REVERSE_PAYOUT_COMPLETE",
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"type": "api",
"reverse_payout_bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": "2789745764564645"
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Reverse Payout Failed Event
This webhook is triggered when a reverse payout attempt fails due to an error or issue during processing.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "ReversePayoutFailed",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "PAYOUT_ACH_FAILED",
"capture_status": "CAPTURE_COMPLETE",
"payout_status": "PAYOUT_ACH_FAILED",
"clawback_status": null,
"reverse_payout_status": "REVERSE_PAYOUT_ACH_FAILED",
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"type": "api",
"reverse_payout_bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": "2789745764564645"
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Reverse Payout Started Event
This webhook is triggered when a reverse payout has been successfully initiated and is in the process of being completed.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "ReversePayoutStarted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "PAYOUT_ACH_FAILED",
"capture_status": "CAPTURE_COMPLETE",
"payout_status": "PAYOUT_ACH_FAILED",
"clawback_status": null,
"reverse_payout_status": "REVERSE_PAYOUT_ACH_PENDING",
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"type": "api",
"reverse_payout_bank_identifiers": {
"credit_bank_id": "ach_11n4zxny1twjdmk",
"credit_trace_id": null
},
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
***
### Transaction Events
Transaction events provide visibility into the full lifecycle of the debit side of a payment—when funds are pulled from
the payer’s bank account. These events allow your platform to monitor the status of ACH debits as they move through
creation, processing, completion, or failure.
Each webhook corresponds to a specific stage of the transaction journey, from initial creation (TransactionStarted) to
settlement (TransactionCompleted), including events for cancellation, failure, and capture. By subscribing to these
events, you can build real-time audit trails, trigger status updates, and automate downstream actions tied to the
movement of funds from the payer.
Transaction webhook payloads include a `bank_identifiers` object containing the debit's `debit_bank_id` and
`debit_trace_id`. Across the nine transaction events documented below, `debit_trace_id` is only assigned once the debit
has settled—it is populated on the **TransactionCompleted** event and is `null` on the other eight.
#### Transaction Awaiting Cancellation Event
This webhook is triggered when a cancellation request for a transaction has been received and the debit ACH has not settled yet.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "TransactionAwaitingCancellation",
"transaction": {
"uuid": "9b97f15d-83bc-41ad-9514-0437180d0192",
"client_reference_id": "2",
"status": "AWAITING_CANCELLATION",
"currency": "USD",
"trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"debit_bank_id": "ach_11n5mnan1v4kk2v",
"debit_trace_id": null
},
"amount": 3245,
"transaction_fee": 0,
"payout_delay_days": 0,
"company_name": "MoneyOne",
"description": "sending money",
"addenda": null,
"type": "api",
"ach_return_code": null,
"cancel_reason": null,
"declined_reason": null,
"payor": {
"uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"user_uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"type": "person",
"name": "John Doe",
"processor_mid": null,
"bank_account": {
"aggregator_type": "manual",
"uuid": "9b97f121-a449-4b52-9f36-6c55f18394d6"
}
},
"payee": {
"uuid": "1a437e1f-c112-4534-ac44-2de71cec9f1f",
"user_uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"type": "business",
"name": "Acme Pest Control",
"processor_mid": null,
"bank_account": {
"aggregator_type": "manual",
"uuid": "9b97f121-a449-4b52-9f36-6c55f18394d6"
}
},
"timestamps": {
"created_at": "2024-06-25 13:57:03",
"updated_at": "2024-06-25 13:57:09",
"canceled_at": null
},
"ach_timestamps": {
"created_at": "2024-03-20 16:00:28",
"processed_at": "2024-03-20 17:00:27",
"sent_at": null,
"settled_at": null,
"failed_at": null,
"cancelled_at": null,
"declined_at": null
}
}
}
```
#### Transaction Capture Started Event
This webhook is triggered when an ACH debit has been successfully initiated as part of a transaction. It indicates that
the funds capture process has begun from the payor’s bank account.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following
```json Event theme={"system"}
{
"event": "TransactionCaptureStarted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "CAPTURE_ACH_PENDING",
"capture_status": "CAPTURE_ACH_PENDING",
"capture_trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"debit_bank_id": "ach_11n5mnan1v4kk2v",
"debit_trace_id": null
},
"payout_status": null,
"clawback_status": null,
"reverse_payout_status": null,
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"cancel_reason": null,
"type": "api",
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Transaction Canceled Event
This webhook is triggered whenever a transaction is canceled before it is processed. It confirms that no funds will be
captured or disbursed for the canceled transaction.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "TransactionCanceled",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "CANCELED",
"capture_status": "CANCELED",
"capture_trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"debit_bank_id": "ach_11n5mnan1v4kk2v",
"debit_trace_id": null
},
"payout_status": null,
"clawback_status": null,
"reverse_payout_status": null,
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"type": "api",
"cancel_reason": "Unable to link bank account.",
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Transaction Completed Event
This webhook is triggered when a transaction has been successfully debited from the payer—meaning the ACH debit from the
payor has been settled, and we have begun the payout process to the payee.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "TransactionCompleted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "PAYOUT_PENDING_ACH",
"capture_status": "CAPTURE_COMPLETE",
"capture_trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"debit_bank_id": "ach_11n5mnan1v4kk2v",
"debit_trace_id": "123456789012345"
},
"payout_status": null,
"clawback_status": null,
"reverse_payout_status": null,
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"cancel_reason": null,
"type": "api",
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Transaction Declined Event
This webhook is triggered when a transaction is declined. The `declined_reason` field on the transaction and the
`declined_at` timestamp within `ach_timestamps` indicate why and when the transaction was declined.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "TransactionDeclined",
"transaction": {
"uuid": "9b97f15d-83bc-41ad-9514-0437180d0192",
"client_reference_id": "2",
"status": "DECLINED",
"currency": "USD",
"trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": null,
"amount": 3245,
"transaction_fee": 0,
"payout_delay_days": 0,
"company_name": "MoneyOne",
"description": "sending money",
"addenda": null,
"type": "api",
"ach_return_code": null,
"cancel_reason": null,
"declined_reason": "Transaction declined by risk review",
"payor": {
"uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"user_uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"type": "person",
"name": "John Doe",
"processor_mid": null,
"bank_account": {
"aggregator_type": "manual",
"uuid": "9b97f121-a449-4b52-9f36-6c55f18394d6"
}
},
"payee": {
"uuid": "1a437e1f-c112-4534-ac44-2de71cec9f1f",
"user_uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"type": "business",
"name": "Acme Pest Control",
"processor_mid": null,
"bank_account": {
"aggregator_type": "manual",
"uuid": "9b97f121-a449-4b52-9f36-6c55f18394d6"
}
},
"timestamps": {
"created_at": "2024-06-25 13:57:03",
"updated_at": "2024-06-25 13:57:09",
"canceled_at": null
},
"ach_timestamps": {
"created_at": "2024-03-20 16:00:28",
"processed_at": null,
"sent_at": null,
"settled_at": null,
"failed_at": null,
"cancelled_at": null,
"declined_at": "2024-03-20 17:00:27"
}
}
}
```
#### Transaction Failed Event
This webhook is triggered when a transaction fails at any stage of processing—whether due to account issues, validation
errors, or ACH return codes.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
If for some reason the payment cannot be successfully processed the error details will be sent in the payload through a
variable called `ach_return_code` along with the corresponding R code in 'ach\_return\_code'. For a list of ACH return
codes see our [Return Codes](/docs/resources/return-codes) documentation.
```json Event theme={"system"}
{
"event": "TransactionFailed",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "CAPTURE_ACH_FAILED",
"capture_status": "CAPTURE_ACH_FAILED",
"capture_trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"debit_bank_id": "ach_11n5mnan1v4kk2v",
"debit_trace_id": null
},
"payout_status": null,
"clawback_status": null,
"reverse_payout_status": null,
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"cancel_reason": null,
"type": "api",
"failed_reason": "Insufficient funds",
"ach_return_code": "R01",
"failure_reason": "Insufficient funds",
"failure_code": "R01",
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
#### Transaction Paused Event
This webhook is triggered when a transaction has been paused, typically due to compliance or risk review processes.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "TransactionPaused",
"transaction": {
"uuid": "9b97f15d-83bc-41ad-9514-0437180d0192",
"client_reference_id": "2",
"status": "PAUSED",
"currency": "USD",
"trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"debit_bank_id": "ach_11n5mnan1v4kk2v",
"debit_trace_id": null
},
"amount": 3245,
"transaction_fee": 0,
"payout_delay_days": 0,
"company_name": "MoneyOne",
"description": "sending money",
"addenda": null,
"type": "api",
"ach_return_code": null,
"cancel_reason": null,
"declined_reason": null,
"payor": {
"uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"user_uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"type": "person",
"name": "John Doe",
"processor_mid": null,
"bank_account": {
"aggregator_type": "manual",
"uuid": "9b97f121-a449-4b52-9f36-6c55f18394d6"
}
},
"payee": {
"uuid": "1a437e1f-c112-4534-ac44-2de71cec9f1f",
"user_uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"type": "business",
"name": "Acme Pest Control",
"processor_mid": null,
"bank_account": {
"aggregator_type": "manual",
"uuid": "9b97f121-a449-4b52-9f36-6c55f18394d6"
}
},
"timestamps": {
"created_at": "2024-06-25 13:57:03",
"updated_at": "2024-06-25 13:57:09",
"canceled_at": null
},
"ach_timestamps": {
"created_at": "2024-03-20 16:00:28",
"processed_at": null,
"sent_at": null,
"settled_at": null,
"failed_at": null,
"cancelled_at": null,
"declined_at": null
}
}
}
```
#### Transaction Resumed Event
This webhook is triggered when a paused transaction has been resumed and is back in processing.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "TransactionResumed",
"transaction": {
"uuid": "9b97f15d-83bc-41ad-9514-0437180d0192",
"client_reference_id": "2",
"status": "CAPTURE_ACH_PENDING",
"currency": "USD",
"trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"debit_bank_id": "ach_11n5mnan1v4kk2v",
"debit_trace_id": null
},
"amount": 3245,
"transaction_fee": 0,
"payout_delay_days": 0,
"company_name": "MoneyOne",
"description": "sending money",
"addenda": null,
"type": "api",
"ach_return_code": null,
"cancel_reason": null,
"declined_reason": null,
"payor": {
"uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"user_uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"type": "person",
"name": "John Doe",
"processor_mid": null,
"bank_account": {
"aggregator_type": "manual",
"uuid": "9b97f121-a449-4b52-9f36-6c55f18394d6"
}
},
"payee": {
"uuid": "1a437e1f-c112-4534-ac44-2de71cec9f1f",
"user_uuid": "f3d8c9d1-0b8b-4e79-bc41-8998cbbd58b1",
"type": "business",
"name": "Acme Pest Control",
"processor_mid": null,
"bank_account": {
"aggregator_type": "manual",
"uuid": "9b97f121-a449-4b52-9f36-6c55f18394d6"
}
},
"timestamps": {
"created_at": "2024-06-25 13:57:03",
"updated_at": "2024-06-25 13:57:09",
"canceled_at": null
},
"ach_timestamps": {
"created_at": "2024-03-20 16:00:28",
"processed_at": null,
"sent_at": null,
"settled_at": null,
"failed_at": null,
"cancelled_at": null,
"declined_at": null
}
}
}
```
#### Transaction Started Event
This webhook is triggered when a transaction has been successfully created in the GrailPay system but ACH processing has
not yet begun. It marks the initial step in the transaction lifecycle.
When this event is triggered, GrailPay sends a webhook payload to your configured URL with the following structure:
```json Event theme={"system"}
{
"event": "TransactionStarted",
"uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
"payer_uuid": "aae3664b-db18-428c-9a2f-a876f259a7c3",
"payee_uuid": "54f964d4-7ade-4cd1-a278-4f21cad18a2b",
"status": "CAPTURE_PENDING",
"capture_status": "CAPTURE_PENDING",
"capture_trace_id": "ach_11jzggrptrftyd",
"bank_identifiers": {
"debit_bank_id": "ach_11n5mnan1v4kk2v",
"debit_trace_id": null
},
"payout_status": null,
"clawback_status": null,
"reverse_payout_status": null,
"currency": "USD",
"amount": 12000,
"transaction_fee": 300,
"client_reference_id": "",
"payout_delay_days": 2,
"cancel_reason": null,
"type": "api",
"created_at": "2023-04-11 14:25:26",
"updated_at": "2023-04-11 18:25:26"
}
```
***
## Testing
At this time, GrailPay does not support direct simulation of webhook events in the Sandbox environment. However, you
can test your webhook integration by registering a webhook URL in Sandbox and performing actions that naturally trigger
webhook events. This approach allows you to validate that your endpoint receives and processes real webhook payloads as
expected.
For more details on testing in Sandbox, refer to our [Technical Overview](/docs/technical/overview#testing) guide.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Manage Webhooks
Source: https://docs.grailpay.com/docs/technical/webhooks/manage-webhooks
GrailPay provides a single endpoint to both register and de-register webhook URLs for event subscriptions. This endpoint
allows you to manage your webhook configurations efficiently and programmatically. By using different request payloads,
you can either subscribe a new URL to specific events or remove an existing one from receiving notifications.
Webhook registrations are scoped to the API token you use, and each token is allowed a maximum of 10 unique webhook URLs.
If you exceed this limit or attempt to register a duplicate, the request will result in an error. Webhook events are
delivered via HTTPS POST requests, and all configured URLs must be secured over HTTPS.
For a list of all possible event names you can subscribe to, please refer to the [Webhook Events](/docs/technical/webhooks/events)
documentation.
This route is secured with **processor** and **vendor** token authentication.
Our webhooks are not specific to any particular entity in our ACH Application. So for
example, you would not want to register an individual webhook for every merchant that you onboard. Each of those
webhooks urls will get the same data each time an event is triggered. The approach we recommend is to register a
single webhook url to the events you wish to subscribe to. Then you can use the event data to determine which
entity the event is related to.
**If our application detects consistent errors from any registered webhook url, we will automatically deregister
that webhook url.**
***
### Registering a Webhook
To register a webhook URL for a specific event, submit a `POST` request to the `/3p/api/v1/webhook` endpoint with the desired
`event_name` and `webhook_url`. Once registered, GrailPay will begin delivering event payloads to the specified URL.
If the URL is successfully registered, you’ll receive a 200 response confirming the subscription.
For full schema details, refer to:
* [Register Webhook](https://api.grailpay.com/api/documentation#/Webhooks/ffad4d3dd269ae7a2b6cd12e5bac2256)
***
### De-Registering a Webhook
To de-register an existing webhook, send a `DELETE` request to the `/3p/api/v1/webhook` endpoint using the same
`event_name` and `webhook_url` that were originally registered. If a matching subscription is found, it will be removed,
and no further webhook events will be delivered to that URL.
For full schema details, refer to:
* [De-Register Webhook](https://api.grailpay.com/api/documentation#/Webhooks/a9141fe6e6da5b72d879e19856392b7c)
***
### Fetch All Webhook Events
This endpoint allows you to retrieve a list of all webhook event types currently supported by the GrailPay API. Each
event in the list corresponds to a specific system action—such as a transaction starting, a refund being completed, or a
payout failing—that can trigger a webhook notification when subscribed to.
This endpoint is useful for dynamically discovering the available webhook events your integration can subscribe to,
especially when building tools for configuration, administration, or reporting. You can use the returned list to ensure
your system is aware of the full range of webhook triggers available for monitoring and automation.
For full request and response details, see the [OpenAPI Documentation](https://api.grailpay.com/api/documentation#/Webhooks/71449a2d892d7bb4362aeab26395ce42).
***
## Testing
At this time, GrailPay does not support direct simulation of webhook events in the Sandbox environment. However, you
can test your webhook integration by registering a webhook URL in Sandbox and performing actions that naturally trigger
webhook events. This approach allows you to validate that your endpoint receives and processes real webhook payloads as
expected.
For more details on testing in Sandbox, refer to our [Technical Overview](/docs/technical/overview#testing) guide.
***
## Questions?
If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).
***
# Welcome
Source: https://docs.grailpay.com/home/welcome