Building Brex Instant Payouts

12 min read Original article ↗

Anant Jain

We recently launched Instant Payouts — a new feature that shortens the time it takes for our customers to access their revenue. In this post, I’m going to do a deep dive into how we built it, as well as give a glimpse into how we move fast in a business where you have to be reliable enough to let your customers trust you with their money.

The Problem

Ecommerce businesses are cash-constrained — the time lag between inventory and advertising purchases, sales, and the revenue then hitting the business's accounts isn’t ideal. For example, a business selling on Amazon will typically have to wait 1–2 weeks from a purchase being made and the funds getting cleared. This is due to these large ecommerce platforms mitigating against chargebacks, fraud, and optimizing for efficiency.

Press enter or click to view image in full size

The implication of this on small businesses is that their growth is either slowed down or they are forced to take short-term bridging loans that often come with high-interest rates and fees.

At Brex, we wondered what if we enable our customers to connect Amazon to Brex and request an “instant payout” to their Brex account whenever they need the cash? And what if this instant cash is made available to be used for free Wire/ACH transfers, or to be spent on the Brex Card, all in under 5 seconds?

With these objectives in mind, we set out to design and implement instant payouts back in July 2020 and delivered the first working version in a little over 2 months. In the next section, I’ll explain how we achieved this.

The Solution

At the start of the project, we focused on Amazon sellers due to the more acute need created by a 2-week payout cycle, and then extended the product to other daily-payouts platforms like Stripe, Shopify, and Paypal. We prioritized in this order because payouts for these secondary platforms are closer to 2–3 days. From a customer perspective, we wanted the user to be able to connect their Amazon account via an API access token so that Brex could get visibility into their up-to-date Amazon Seller account. This enabled Brex to give them an instant payout “offer”, which is a percentage of their expected upcoming payout that they could deposit into their account instantly for a small fee. Additionally, we wanted to let the customer offset the fee with the rewards points they already earned on Brex, making the offer effectively free. Here’s what this flow looks like:

Product Deep Dive

Under the hood, instant payouts work as a “receivables factoring arrangement.” It simply means that in exchange for the instant payout, Brex buys the rights to the revenue from sales (up to the requested payout) that the customer has made on Amazon so far. We make this arrangement explicit with a report detailing every platform sale that Brex is purchasing in exchange for the cash being deposited into the user’s Brex Cash account. When the payout finally reaches the customer’s Brex Cash account, Brex collects the amount due and releases the rest to the customer.

Press enter or click to view image in full size

With this context, we can list down some requirements we needed to satisfy to be able to ship the very first version of this product:

  1. The customer should be able to connect their Amazon account to Brex and get instantly onboarded (no more than a few minutes).
  2. Brex should be able to validate whether the user has been depositing their Amazon sales to their Brex Cash account.
  3. The core dashboard experience should be simple and intuitive. The customer should be able to see an available instant payout offer and accept it, with the money getting deposited in the Brex Cash account instantly.
  4. The user should be able to offset the instant payouts’ fees with their Brex rewards points. These rewards points are earned when the user spends on their Brex Card, thereby making the product effectively free to use.
  5. When a user accepts an instant payouts offer, we need to “prepare” it as per the factoring arrangement, i.e., we need to allocate orders to the offer being accepted so that Brex can legally collect against the payout once it lands in the customer’s Brex Cash account.
  6. Once the user deposits an instant payout, Brex should move money from a funding account to the customer’s Brex cash account (an internal transfer), and ensure that accountants at all companies (our e-commerce customers, as well as Brex) are happy :)
  7. Brex should be able to detect when the payout from Amazon arrives into the customer’s Brex Cash account and collect any outstanding amount from Brex Cash. Additionally, Brex should be able to collect from a linked bank account if the payout is insufficient to cover the deposit.
  8. Brex should manage risk well. There are multiple reasons why we may be unable to collect from an incoming payout, including fraud. We manage risk by requiring sellers to meet certain thresholds in terms of selling history, return/refund rate, etc., and give them an offer of about 80% of their expected payout to further protect Brex.
  9. The entire system should work with zero manual intervention in the happy path: we want to auto-approve a good customer, generate an offer for them, and let them accept it with no manual intervention, with the end-to-end process taking a few minutes, not a few hours.
  10. Our support teams should be able to monitor the status of all customers, get notified of any delinquencies, and initiate any necessary internal/external collections.

It was evident from the list above that shipping instant payouts was going to be a massive cross-functional effort with multiple teams pitching in. In the next section, we will give an overview of the architecture behind this product.

Architecture

A system’s architecture is best explained by a diagram. Here’s a simplified version of the final design that we ended up using for instant payouts:

Press enter or click to view image in full size

Let’s walk through this diagram together:

  1. The Data Importer service uses the customer’s Amazon credentials to import data about their past and upcoming payouts and sales. It also requests Amazon to generate the customer’s “Seller Health” reports that we use to determine whether a customer qualifies for instant payouts or not. This service stores the imported data in durable storage (Amazon S3, in our case) and publishes a Kafka event notifying the Data ETL service that a new S3 file has been created for processing.
  2. Like most services at Brex, the Data ETL service has its own isolated DB. This service consumes the aforementioned event from the Data Importer service, downloads the S3 file, processes the data, and stores the parsed data in an OLTP DB (Postgres, in our case).
  3. The Offers Engine is a stateless service that takes in requests to generate an offer for the customer. It also runs various onboarding checks that determine whether a customer is eligible for instant payouts. An interesting architecture choice here was to keep this service asynchronous by queuing requests in a Celery queue and consuming them in batches. The service has read-only access to various data sources across Brex (including the Data ETL DB), and can also request data from other services through their RPC endpoints.
  4. The Offers service is the central point of coordination of the system. It issues onboarding checks requests / offers generation requests to the Offers Engine, orchestrates the movement of money to and from the customer’s account upon advances and collections, and manages customer account status.
  5. Brex has a separate API service that creates GraphQL endpoints that our frontend consumes. This service makes RPC calls to the Offers service for most of its needs.
  6. The Treasury services are the source of truth on what a customer owes Brex across all its products. It also manages a Payments service that exposes an API that the Offers service can use to collect from several sources including their Brex Cash account, external bank accounts via ACH, and their rewards points.
  7. The Cash services perform the actual money movement from a Brex-owned funding account to the customer’s Cash account once the instant payout offer is accepted. Additionally, the Cash services are the first to know about an incoming transaction, check if it can possibly be an incoming Amazon payout for a customer that previously accepted an instant payout, and lock and collect the outstanding balance from it before releasing the remaining amount.

This is a broad overview of how the product works. Next, we will discuss some interesting design decisions that went into this architecture.

Interesting Design Decisions

Architectural decisions for any involved system design don’t take place in a vacuum. As a small team of 4, we ended up spending almost ~2 weeks writing our design docs, debating multiple tradeoffs, and getting input from the rest of the Brex Engineering group on our architectural choices. Here’s a handful of questions that engendered the most engaging discussions within our team:

  • What is a good data storage choice/architecture for the Data ETL service? A possible alternative here was to store the raw imported data in a data warehouse like Snowflake, batch-perform aggregations/computations using a suitable map-reduce solution like Apache Spark, and then store these computations in a DB. After doing some back-of-the-envelope analysis, we realized that a simpler architecture where we do this ETL without a map-reduce would suffice for a start, and we can transition to the alternative when we hit scaling bottlenecks.
  • What is a good boundary between Offers service and Offers Engine service, and do we even need to have a separate Offers Engine service? A key thing to note here is that most of the “data” services at Brex are written in Python, while the rest of the backend services are in Elixir/Kotlin/Go, etc. We decided that we should build the Offers Engine service in Python, thereby making it easier to adapt it to use fancier machine learning models for generating offers in the future. Additionally, smaller, focused, micro-services are better, and we worried that Offers service will become a monolith if we don’t aggressively push for smaller services from the get-go.
  • Should we validate that a customer’s sales are getting deposited in Brex Cash (or an external bank account) within the Offers service, or should we spin up a new service for it? We always knew that we needed a more generic solution that can validate whether a customer is using Brex Cash (or an external bank account) as an “operational” account, i.e., they deposit their sales/payouts to it. For the initial version, we decided that we can keep this logic Brex Cash and Amazon-specific, and it can live within the Offers service. As a fast-follow, we migrated the sales deposits validation part to its own service with its own isolated DB. We will cover the architecture of this service in a follow-up post.
  • How do we make interactions between services as asynchronous as possible? This one was easy for our team since the talented Foundations team at Brex has set up a robust events infrastructure based on Kafka that enables services at Brex to publish/consume events reliably. The system is mature enough to provide at-least-once delivery guarantees, delayed retries, dead-letter queues, and consistently exceeds its SLO’s for events delivery. Making service interactions asynchronous means that the events can easily be retried in case of consumer/service failures, the consuming service has a choice to batch consume, the publisher code is simpler in case multiple services need to consume the same event, and acts as a buffer during inter-service traffic spikes.
  • How do you protect Brex against imperfect e-commerce API data? We all know that APIs are not perfect. Since we were building a product that deposits money based on API integrations, we had to do a lot of work to ensure our future payout estimates were accurate, especially in a system with a ton of moving parts. Further, we needed to also add monitoring checks, dynamic offer caps, a mechanism to automatically make offers zero in case of any detectable unusual activity, and so on to better protect Brex in an automated way.

What’s next?

It took Brex less than 4 months from the first conception of the “instant payouts” idea to a generally available product where an ecommerce company could sign up for Brex in 5 minutes, get instantly approved for a Cash account with free ACH, wires, etc., and start accessing their unpaid Amazon revenue.

However, we have a massive opportunity ahead of us. The problem of delayed payouts is not limited to ecommerce businesses. If you’re a mobile developer selling on Apple App Store or Google Play Store, it can take up to 45 days for your money to be sent to your bank account. At Brex, one of our values is to inspire customer love. When customers and those around us love what we do and advocate on our behalf, our potential is unbounded. Identifying deep pain points for our customers, and solving them with urgency and focus is what makes Brex a loved product, and in turn, makes working at Brex a fulfilling adventure.

We’re constantly looking for ways to move fast without cutting corners, and are always on the lookout for talented people who share our ambition. If you’re one of those, please check out our Open Roles at https://www.brex.com/careers/

_______________________________________________________________

We’d like to acknowledge that designing and building instant payouts was a massive team effort, with the other early members of Capital Engineering Aditya Durvasula, Luis Fernando Varela Eleta, and Prabhav Jain contributing heavily to these design decisions. Additionally, we had support and guidance from the rest of the Brex engineering organization, in particular, the talented engineers on the Cash, Treasury, and Data teams.