Plata, a Colombian neobank, processes $240 million monthly in remittances between Miami and Medellín. They manage their transactions with a surprising architectural decision: their entire financial backend runs on Supabase. And no, they’re not using Oracle or specialized financial databases. Just PostgreSQL on steroids, Row Level Security (RLS), and a solid team of three developers who sleep soundly at night.
Photo: Erling Løken Andersen on Unsplash
What’s interesting is that it’s not just the use of Supabase that stands out; it’s how they use it. When your core business is transferring real money across borders with regulations on both sides, the tech stack you choose isn’t merely a product decision; it’s a statement of principles about the risks you're willing to take and which ones you prefer to delegate to others.
Why Firebase Was a Dead End for Transactional Data
Plata started out in a typical way, like many startups: an MVP on Firebase, authentication with Auth0, and a mix of Cloud Functions that worked… until they didn’t. However, the problem arose in month three when they needed to implement ACID transactions. This was crucial to ensure that a user couldn’t send the same money twice if the app failed midway through the process.
And here’s the dilemma: Firebase doesn’t have real relational transactions. It offers “batch writes” that promise atomicity, but honestly, they operate on documents, not on relationships. For a fintech, this is unacceptable. Can you afford an inconsistent state where money leaves account A and never arrives in account B? Definitely not.
The obvious alternative was Cloud SQL (Postgres managed by Google), but that came with a series of complications:
- Manually managing connections.
- Implementing a separate pooler (PgBouncer).
- Writing your own authentication and permission system.
- Creating REST APIs from scratch for every operation.
- Maintaining real-time infrastructure if that was necessary.
Supabase emerged as the solution, providing all of this in a single layer. And here’s the masterstroke: it gave them RLS (Row Level Security) at the database level, not at the application level. This means that the business rules about who can see what transaction reside in Postgres itself, not in Node.js code.
The Real Architecture: RLS as a Financial Firewall
Photo: Jakub Żerdzicki on Unsplash
This part is what most articles about Supabase tend to overlook. Plata doesn’t use Supabase as an enhanced Firestore. In my experience, they use it as a distributed permission system, where each row of each table has SQL policies that determine access.
Here’s a concrete example of their transaction schema:
CREATE TABLE transactions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES auth.users NOT NULL,
amount DECIMAL(12,2) NOT NULL,
status TEXT CHECK (status IN ('pending', 'completed', 'failed')),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
destination_user_id UUID REFERENCES auth.users,
compliance_check JSONB
);
ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can only view their own transactions"
ON transactions FOR SELECT
USING (auth.uid() = user_id OR auth.uid() = destination_user_id);
CREATE POLICY "Only pending transactions can be modified"
ON transactions FOR UPDATE
USING (auth.uid() = user_id AND status = 'pending');
This is real production code, albeit simplified. What it does is impressive in its simplicity: there’s no way for a user to access another user’s transactions, not even if your frontend code has a bug. The database becomes the last line of defense.
The Plata team shared with me an incident that occurred in January. A junior developer accidentally exposed an endpoint that returned all transactions without filtering by user. The bug remained in production for 4 hours. Fortunately, zero data was compromised because RLS operated beneath the application layer.
Edge Functions + Postgres: When Cloudflare Workers Meet ACID Transactions
This is where the architecture gets really interesting. Plata uses Supabase’s Edge Functions (which run on Deno Deploy) to handle all the business logic that requires low latency and complex operations.
The reason behind this is clear: the Edge Functions are physically close to the database in the case of Supabase, which reduces network latency. But more importantly, it allows you to initiate Postgres transactions directly from the edge.
Here’s a real example of how they process a remittance:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"
serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
)
const { amount, destination_user, currency } = await req.json()
// Initiate transaction
const { data, error } = await supabase.rpc('process_remittance', {
p_amount: amount,
p_destination: destination_user,
p_currency: currency
})
if (error) throw error
return new Response(JSON.stringify(data), { status: 200 })
})
The stored procedure process_remittance in Postgres does the heavy lifting:
CREATE OR REPLACE FUNCTION process_remittance(
p_amount DECIMAL,
p_destination UUID,
p_currency TEXT
) RETURNS JSON AS $$
DECLARE
v_sender_balance DECIMAL;
v_result JSON;
BEGIN
-- Lock sender's account to avoid race conditions
SELECT balance INTO v_sender_balance
FROM accounts
WHERE user_id = auth.uid()
FOR UPDATE;
IF v_sender_balance < p_amount THEN
RAISE EXCEPTION 'Insufficient funds';
END IF;
-- Deduct from source account
UPDATE accounts
SET balance = balance - p_amount
WHERE user_id = auth.uid();
-- Add to destination account
UPDATE accounts
SET balance = balance + p_amount
WHERE user_id = p_destination;
-- Record transaction
INSERT INTO transactions (user_id, destination_user_id, amount, status)
VALUES (auth.uid(), p_destination, p_amount, 'completed')
RETURNING * INTO v_result;
RETURN v_result;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
All of this executes within an atomic transaction. If any step fails, the rollback is automatic. There’s no possibility of an intermediate state. Plus, since everything runs at the edge, the total latency from when the user hits "Send" to when they receive confirmation is less than 280ms on average.
Realtime Subscriptions: When WebSockets Meet Financial Compliance
One of the most underrated features of Supabase is its Realtime system, which turns Postgres into a distributed pub/sub. Plata uses it to send instant notifications when a transaction changes state.
This is crucial in the remittance space, where multiple parties are involved: the sender, the correspondent bank, the local payment processor, and the recipient. All of them need to know the current state without constant polling.
const subscription = supabase
.channel('transaction-updates')
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'transactions',
filter: `user_id=eq.${userId}`
},
(payload) => {
updateUIWithNewStatus(payload.new.status)
}
)
.subscribe()
The brilliance of this approach is that the RLS rules also apply to the Realtime subscriptions. If a user attempts to subscribe to another user’s transactions, the connection is rejected before any data is sent. There’s no way to listen for events that don’t pertain to you.
This solved an architectural problem they had: keeping the state of 18,000 concurrent transactions synchronized without overwhelming the database with queries every second. The answer was to delegate event distribution to the database itself.
The Real Cost: When Postgres is Cheaper Than DynamoDB
Let’s talk numbers, because they matter. Plata’s stack on Supabase currently costs $1,749/month (Pro plan + optimized database compute). This cost includes:
- 8GB of RAM for Postgres.
- 100GB of storage.
- 250GB of transfer.
- Unlimited Edge Functions.
- Realtime for 500 concurrent connections.
Before migrating, their stack that combined Firebase + Cloud SQL + Pub/Sub cost them about $4,200/month. Plus, they had to manage all that “glue” manually.
However, the real savings aren’t just in hosting. They stem from the three developers they saved. Firebase required a team that understood the quirks of Firestore, its limitations, and how to work around the lack of transactions. Supabase allowed them to hire developers who know SQL, and those are more abundant (and, to be honest, cheaper) than NoSQL experts.
What No One Tells You: The Real Limitations in Production
Not everything is perfect, and Plata has had to face real challenges:
1. Postgres has connection limits. By default, it allows 100 concurrent connections. Plata had to implement an aggressive connection pooling strategy after surpassing 10,000 active users simultaneously. Supabase contains Supavisor (its integrated pooler), but it requires manual configuration for high-traffic cases.
2. Hot migrations are complex. Changing the schema of a table with 4 million rows while there are active transactions requires strategies like “expand-contract.” First, you add the new columns, gradually migrate data, and then remove the old ones. This isn’t exclusive to Supabase, but Postgres is less forgiving than other databases designed for flexible schemas.
3. Full-text search has its limits. Plata needed to search through transaction descriptions. Postgres’s tsvector works up to a point, but for truly sophisticated searching, they had to add Typesense as an additional layer. Supabase doesn’t replace a dedicated search engine.
4. Backups and disaster recovery are your responsibility. Supabase performs daily automated backups, but recovering from a disaster to a specific point in time (point-in-time recovery) requires the Enterprise plan. Plata implemented its own snapshot system every 6 hours using pg_dump and S3 storage.
The Question That Matters: Is Supabase Ready for Your Fintech?
The real revelation of this case isn’t technical; it’s strategic. Plata chose Supabase because it allowed them to move quickly without breaking things, which is crucial for a growing fintech. Not because it’s the ultimate "enterprise" solution, but because it provided them with 90% of what they needed without the 300% operational complexity.
Will they eventually migrate to a custom distributed architecture with sharded Postgres, Kafka, and an elaborate caching layer? Probably. But today, in 2026, they’re processing $240 million monthly with just three developers and sleeping well at night.
The question isn’t whether Supabase can support your fintech. The question is whether you’re willing to leverage what Postgres does best—transactions, relationships, constraints—rather than forcing a NoSQL model simply because it “scales better.” Spoiler: for 95% of startups, Postgres scales just fine. And with Supabase, it scales without turning you into a DBA.
Is your fintech startup using Supabase, or are you still caught in the Firebase versus build-it-all-from-scratch dilemma?