Migrating from Trolley to Lumanu
This guide is for platforms on Trolley (formerly Payment Rails) that pay vendors, and are evaluating a move to Lumanu.
Required ReadingIf you haven't yet, start with Why wallet-based payments?. This guide focuses on the mechanics of migrating off Trolley.
The short version
Trolley and Lumanu are conceptually closer than most payout-provider comparisons. Both:
- Separate vendor onboarding from payment creation
- Have vendors manage their own identity, banking, and tax info
- Handle tax form collection and year-end reporting
- Use webhook-driven event models
- Require pre-funding before payments can clear
The two things that genuinely change:
- Destination. Trolley pushes funds to a vendor's bank account or PayPal. Lumanu credits a wallet the vendor owns; they choose how and when to withdraw.
- Identity scope. A Trolley recipient exists per-merchant. The same creator on two Trolley platforms is two separate records, each onboarding independently. A Lumanu vendor exists once in the network; they verify with Lumanu and can receive payments from any buyer without re-verifying.
Most of the rest of the migration is a direct object-for-object mapping.
Mental model shift
From pushed payouts to credited wallets
In Trolley, a batch reaching processed means money has been sent (or is en route) to the vendor's configured payout method: bank account, PayPal, or check. The vendor's relationship with the destination is your problem: bounced payments, returned funds, incorrect account numbers, retries, currency conversion mismatches.
In Lumanu, a funded payable means the vendor's wallet balance has gone up. The vendor owns the wallet. They pick when to withdraw, where to withdraw to, and how much. Bank-level failures happen between the wallet and the bank, not between your platform and the bank.
The practical consequence: the payment.failed and payment.returned handling code you've written for Trolley goes away. Those states still exist somewhere, but they're no longer in your webhook pipeline.
From merchant-scoped to network-wide vendors
This is the bigger structural shift, and the reason many platforms move.
In Trolley, vendor onboarding is per-merchant. If a creator works with five platforms on Trolley, they fill out five W-9s, verify five times, and keep five sets of bank details in sync. Your support team absorbs its share of the friction.
In Lumanu, vendors exist once in the network. A creator who's worked with any other Lumanu platform is already verified when you invite them. The invite attaches them to your workspace without re-onboarding. New vendors go through verification once, then are available to every platform in the network going forward.
Your support team stops fielding "I already sent my W-9 to the other platform, why do I need to do it again?" tickets.
Terminology map
| Trolley | Lumanu | Notes |
|---|---|---|
| Merchant account | Workspace | Your buyer account. Holds a wallet with its own account/routing number. |
| Recipient | Partner / Vendor | Network-wide in Lumanu, not merchant-scoped. |
| RecipientAccount (payout method) | None | Not exposed to your platform. Vendor picks from their wallet. |
| Payment | Payable | A single payment obligation to a vendor. |
| Batch | Funding (with payable_ids) | Funding groups payables for payment; plays the role of a batch. |
start-processing on a batch | POST /payable/{id}/approve + POST /funding | Approval and funding are separate steps in Lumanu. |
| Account balance | Workspace wallet balance | GET /workspace/{id}/wallet. Same pre-funding model. |
| Tax form (W-9/W-8BEN) collection | Handled by Lumanu network | Vendor submits once, used across all buyers. |
| 1099-NEC / 1042-S from your merchant | Issued by Lumanu | Lumanu is the reporting party as Master Vendor. |
| Webhook subscription | Webhook subscription | POST /workspace/{id}/webhook-subscription |
API mapping
Onboarding a vendor
Trolley:
// Create the recipient
const recipient = await client.recipient.create({
type: 'individual',
firstName: 'Alex',
lastName: 'Creator',
email: '[email protected]',
// ...additional required fields
});
// Recipient completes onboarding via Trolley Widget or portal
// Poll/webhook on recipient.updated until status === 'active'Lumanu has two onboarding paths. Pick the one that matches your UX:
Option A: Server-side invite (Lumanu sends the email):
await fetch(`${LUMANU_BASE}/workspace/${workspaceId}/partner/invite`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
invites: [{
payee_email: '[email protected]',
payee_business_name: 'Creator LLC', // optional
}],
}),
});Option B: Redirect flow (Trolley Widget equivalent):
// Fetch the workspace to get its vendor invite URL
const res = await fetch(`${LUMANU_BASE}/workspace/${workspaceId}`, {
headers: { Authorization: `Bearer ${token}` },
});
const { vendor_invite_url } = await res.json();
// Redirect the creator, same UX as the Trolley Widget sign-up flow
// Example URL:
// https://use.lumanu.com/public/invitation/get-paid?invitationId=<workspace-id>
window.location.href = vendor_invite_url;The vendor_invite_url is stable per workspace (cache it) and reflects your branded signup tag (bsp_tag) if you've configured one.
Differences worth noting:
- You don't collect personal info on the invite. Just an email (and optionally the business name on Option A). Everything else happens on the Lumanu side.
- If the vendor is already in the Lumanu network, the invite attaches them immediately, with no verification wait.
- For Option A, you can batch invites in a single call by including multiple entries in the
invitesarray.
Checking vendor status
Trolley:
const recipient = await client.recipient.find(recipientId);
if (recipient.status === 'active') {
// ok to pay
}Lumanu:
const res = await fetch(
`${LUMANU_BASE}/workspace/${workspaceId}/partner/${partnerId}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const partner = await res.json();Worth noting: Lumanu lets you create payables against unverified vendors. They sit in approved status and clear once the vendor completes verification. You don't have to gate your product flow on verification state the way you might with Trolley. Queue payables up front and they resolve the moment the vendor is ready.
Sending payments
Trolley (create batch, add payments, start processing):
// Create batch with payments inline
const batch = await client.batch.create({
description: 'Q2 2026 creator payouts',
payments: [
{
recipient: { id: recipientId },
sourceAmount: '1500.00',
sourceCurrency: 'USD',
memo: 'Q2 Campaign Content Creation',
},
],
});
// Process it
await client.batch.startProcessing(batch.id);Lumanu (create payable, approve, fund):
// 1. Create payable
const create = await fetch(`${LUMANU_BASE}/payable`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
workspace_id: workspaceId,
payee_email: '[email protected]',
amount: 150000, // cents
amount_denomination: 'us_cents',
description: 'Q2 Campaign Content Creation',
due_date: '2026-05-01T00:00:00Z',
send_invite: true,
line_items: [{ amount: 150000, description: 'Instagram Content Creation' }],
}),
});
const { payable } = await create.json();
// 2. Approve
await fetch(`${LUMANU_BASE}/payable/${payable.id}/approve`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
// 3. Fund the payable (groups multiple payables like a Trolley batch)
await fetch(`${LUMANU_BASE}/funding`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
workspace_id: workspaceId,
method: 'balance', // draws from existing wallet balance
amount: 150000,
amount_denomination: 'us_cents',
description: 'Q2 2026 creator payouts',
payable_ids: [payable.id],
}),
});A Lumanu Funding plays the role of a Trolley batch: it's the unit of "pay this group of obligations." The difference: payables exist as first-class objects outside any funding. You can create and approve them individually, then group them into a funding when you're ready to pay. This separation is useful if your product has approval workflows where creating the obligation and settling it are distinct events.
For a direct equivalent of Trolley's "one batch, many payments" flow, create N payables and include all their IDs in a single POST /funding call.
Pre-funding the workspace
Both Trolley and Lumanu require your account to have balance before payments clear. In Trolley you fund via wire/ACH to your Trolley account. In Lumanu you have two options:
- Wallet balance funding: transfer into the workspace wallet's account/routing numbers, then use
method: 'balance'on fundings. Mirrors Trolley's pre-funding model. - Invoice funding: Lumanu generates a funding invoice tied to specific payables. Your AP pays it; the deposit arrives unlinked; you match it to the funding; payables clear (assuming they're not also waiting on vendor verification). Useful for enterprise AP workflows that run on PO numbers.
The invoice flow has four steps:
// 1. Create the funding (generates an invoice)
const fundingRes = await fetch(`${LUMANU_BASE}/funding`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
workspace_id: workspaceId,
method: 'invoice',
amount: 5000000,
amount_denomination: 'us_cents',
description: 'April 2026 creator payouts',
po_number: 'PO-12345',
payable_ids: payableIds,
}),
});
const { funding } = await fundingRes.json();
// 2. Retrieve the invoice PDF for your AP team
// GET /funding/{id}/pdf-url
// 3. AP pays via bank transfer to the workspace wallet's
// account/routing numbers. The deposit arrives unlinked.
// 4. Match the deposit to the funding
const unlinkedRes = await fetch(
`${LUMANU_BASE}/workspace/${workspaceId}/wallet/deposit`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const { deposits } = await unlinkedRes.json();
await fetch(`${LUMANU_BASE}/funding/${funding.id}/link-deposit`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ deposit_id: deposits[0].id }),
});Once linked, payables in the funding settle. If they're also waiting on vendor verification, they settle the moment the vendor completes onboarding. The matching step has no Trolley analog: Trolley users pre-fund, and the balance goes down. Plan for either automating the match (poll unlinked deposits, match on amount and timing) or handling it manually via your finance UI.
Webhooks
| Trolley event | Lumanu equivalent |
|---|---|
recipient.updated | Partner status change events |
payment.processed | Payable status → paid |
payment.failed | Not surfaced. Belongs to vendor's wallet-to-bank. |
payment.returned | Not surfaced. Belongs to vendor's wallet-to-bank. |
batch.processing | Payable status → will_pay (after funding) |
batch.processed / batch.completed | Funding status + aggregate payable statuses |
Subscribe via POST /workspace/{id}/webhook-subscription. The two events you lose (payment.failed, payment.returned) reflect bank-side outcomes that are no longer your concern. They happen between the vendor's wallet and their chosen withdrawal destination.
Flow comparison
Onboarding a creator
Trolley:
Platform → POST /v1/recipients → Trolley
Platform → redirect creator to Trolley Widget/portal
Creator → submits ID, bank, tax info → Trolley
Trolley → recipient.updated webhook → Platform
Platform → check status === 'active', mark ready
Lumanu (Option A, server-side invite):
Platform → POST /partner/invite → Lumanu
Lumanu → emails creator (invite + onboarding)
Creator → onboards to Lumanu network (once, ever)
Platform → optionally poll partner status
Lumanu (Option B, redirect flow, Widget equivalent):
Platform → GET /workspace/{id} (reads vendor_invite_url)
Platform → redirect creator to vendor_invite_url
Creator → onboards to Lumanu network (once, ever)
Platform → optionally poll partner status
For a creator already in the Lumanu network, the flow collapses to just the first step. Your invite attaches them to your workspace and they can be paid immediately.
Paying a creator
Trolley:
Platform → POST /v1/batches (with payments) → Trolley
Platform → POST /v1/batches/{id}/start-processing → Trolley
Trolley → processes payment to recipient's bank/PayPal
Trolley → payment.processed webhook → Platform
(or payment.failed / payment.returned on error)
Lumanu:
Platform → POST /payable → Lumanu
Platform → POST /payable/{id}/approve → Lumanu
Platform → POST /funding (balance or invoice) → Lumanu
Lumanu → credits vendor's wallet
Vendor → withdraws on their schedule, their rail
(Platform stops being involved here)
Data migration
Vendors
Export vendors from Trolley (GET /v1/recipients) and invite each to your Lumanu workspace.
// Pseudocode: adapt to your Trolley SDK of choice
const activeRecipients = [];
for await (const r of client.recipient.search('')) {
if (r.status === 'active') {
activeRecipients.push({
payee_email: r.email,
payee_business_name: r.businessName || `${r.firstName} ${r.lastName}`,
});
}
}
// Batch-invite to Lumanu (split into chunks if the list is large)
await fetch(`${LUMANU_BASE}/workspace/${workspaceId}/partner/invite`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ invites: activeRecipients }),
});Identity, banking, and tax info
Don't migrate these. Even if Trolley allowed full export (it doesn't for sensitive fields), Lumanu's network-level verification re-runs independently. Vendors re-verify once with Lumanu, then are set for every Lumanu platform they join.
Plan the vendor communication carefully. The easiest migration is one where:
- Vendors hear from you before the invite email arrives
- The invite email arrives with a clear subject line tied to your platform
- Your in-app experience surfaces the
vendor_invite_urlas a "Set up Lumanu payments" button for vendors who'd rather onboard from inside your product than via email
Platforms that do this well see onboarding completion rates noticeably higher than those relying on the invite email alone.
In-flight payments
- Processed batches. Nothing to do; they're settled.
- Batches still in
processingorpendingstatus. Let Trolley finish them. Don't try to cancel mid-flight unless you're willing to reconcile manually. - Future obligations. Create as Payables in Lumanu from cutover forward.
Lock the Trolley batch-creation path behind a feature flag at cutover so nothing new goes out via Trolley while you're migrating routing logic.
Feature parity and gaps
Things Lumanu does that Trolley doesn't:
- Network-wide vendor identity. Creators onboarded once receive payments from any buyer in the Lumanu network, eliminating per-platform re-onboarding.
- Network-level tax reporting. Lumanu is the 1099/1042-S reporting party; your platform stops being one.
- Separated payable and funding lifecycle. Payables exist as first-class objects outside any funding, which maps cleanly to approval workflows that need to create an obligation before settling it.
Things Trolley does that Lumanu doesn't (or does differently):
- Direct rail control. In Trolley you decide which payout methods to enable (bank, PayPal). In Lumanu, the vendor picks from within their wallet; your platform isn't in that decision.
- Per-payment tagging with rich metadata. Trolley's tag system is quite flexible. Lumanu has
custom_fieldson payables andproject_idfor grouping: similar intent, somewhat less flexibility. - Visible payment failure/return handling. Not surfaced to your platform in Lumanu because it happens after the wallet credit, not before. If you rely on these events for internal reconciliation, you'll need to adapt.
- OfflinePayment records. If you use Trolley's offline payment tracking for non-Trolley payouts, there's no direct Lumanu equivalent.
Rollout strategy
A pattern that works for Trolley → Lumanu specifically:
- Week 0: sandbox integration. Build against
https://api.demo.lumanu.link/api/rest. Verify partner invites, payable lifecycle, and funding flows. UsePOST /workspace/{id}/wallet/depositto simulate funded wallets. - Week 1: dual-write for new vendors. New creators signing up route to Lumanu onboarding instead of Trolley. Existing creators remain on Trolley.
- Weeks 2–4: existing vendor communication and invites. Email your Trolley vendors explaining the change. Send Lumanu invites. Expect staggered completion; don't block on 100%.
- Week 5+: per-creator routing cutover. As each creator completes Lumanu onboarding, flip a per-creator flag routing their future payments to Lumanu. Keep Trolley as the fallback for stragglers.
- Long tail: deadline for Trolley sunset. Pick a cutoff 60–90 days after first invite. Creators who haven't migrated by then are paid one final run through Trolley with a "this is your last payment via this method" notice.
Because Trolley already requires active vendor status before payment, your existing "is this creator payable?" logic maps reasonably directly. Just change what you're checking.
Gotchas
- Tax reporting transition year is messy. In your cutover year, some vendors will have earnings reported by your Trolley merchant and some by Lumanu. Loop your finance/tax team in early so 1099 reconciliation at year-end isn't a surprise.
- "Payment failed" copy disappears. If your product has UI copy about payments failing and needing retry, much of it becomes irrelevant. Audit support macros, FAQ pages, and in-app messaging. Keep relevant copy about verification (still possible to fail) and cut copy about bank rejections (no longer your domain).
- Vendor-facing branding shifts. In Trolley, vendors interact with your merchant name throughout. In Lumanu, the wallet is branded Lumanu, though payables reference the buyer (you). Vendors may ask why "Lumanu" is on emails when they work with your platform. A short explainer in your creator docs prevents support tickets.
- Rate limits are different. Lumanu's standard tier is 100 requests/minute. If you're currently batching in a way that relies on Trolley's higher limits, plan chunking accordingly or ask about enterprise tiers.
- Sandbox behavior diverges from production on funding. The sandbox can simulate deposits via
POST /workspace/{id}/wallet/deposit; production requires real funds arriving via bank transfer. Never test production flows by expecting sandbox-like deposit simulation.
Resources
- API Reference - Full endpoint details
- Sandbox:
https://api.demo.lumanu.link/api/rest - Production:
https://api.lumanu.com/api/rest
Updated 11 days ago
If you haven't read the foundation page go to "Why wallet-based payments?".