Migrate billing from Stripe

Move customer billing from Stripe to Wingspan. Create your Payers, bring saved cards over through Stripe's card data export, carry existing ACH debit authorizations forward, and start invoicing in Wingspan on a date you pick.

This guide is for platforms that bill business customers through Stripe, with invoices, saved cards and ACH debit, and are moving that billing to the Wingspan V3 API. You pick a cutover date. Invoices you create after it are Wingspan invoices, and invoices already open in Stripe finish in Stripe.

Most customers keep the payment method they already have on file. Saved cards come over through Stripe's card data export, and bank accounts come over with the debit authorization the customer already gave you.

How Stripe billing maps to Wingspan

StripeWingspanWhat changes
CustomerPayerYour record of who you bill. Put the Stripe customer ID or your own customer ID in externalId. A Payer doesn't need a Wingspan login.
A customer with several locationsOne Payer with a PayerEngagement for each locationEach engagement can have its own payment method and automatic collection setting.
InvoiceInvoiceYou create it, then send it, which opens it.
Invoice itemLine itemEach line takes metadata, so your own IDs stay on the line.
days_until_duedueDateYou work out the due date from the customer's terms and send it on the invoice.
collection_method: charge_automaticallyAutomatic collectionWe charge the customer's default method on or after the due date.
Card PaymentMethodPayment cardWe save it for the Payer and use it for automatic collection.
us_bank_account PaymentMethod and its MandateExternal bank account and MandateThe mandate records the customer's authorization to debit their account.
Paid out of bandPay off platformRecord a check or wire the customer paid you directly.
RefundRefundYou record a full or partial refund on the invoice.
Card fee settingscreditFeeHandlingSets who pays the card processing fee on each invoice.
Stripe's customer emailsWingspan's invoice emailsTurn them off per invoice with notificationPreferences if you send your own.
invoice.paid, invoice.payment_failedInvoice.Paid, Invoice.PaymentFailedSee Track payments.

What carries over

Customer records

Create a Payer for each Stripe customer from the customer's ID, email, name and metadata. You pull these from Stripe's API. The customer doesn't do anything.

Saved cards

Stripe sends saved cards in an encrypted export, directly to Wingspan's card processor. Card numbers never pass through your systems. Three things to know about the export:

  • Stripe sends it only to a PCI DSS Level 1 processor. We give you that processor's attestation of compliance and PGP key to pass to Stripe.
  • Stripe leaves out cards saved through Link.
  • The export holds cards and customer records. Payment history stays in Stripe.

The export carries each card's Stripe customer ID and metadata. We use the customer ID to attach each card to the Payer whose externalId matches it. Stripe describes the process in Request a payment data export.

Bank accounts

Stripe documents its export for cards. Ask Stripe's migrations team to include your customers' US bank accounts in the same export. If you collected bank details yourself, create the bank accounts through the API instead, as Step 3 shows.

Debit authorizations

Your customer gave the debit authorization to your business. You record it on Wingspan as a mandate, with evidence of the original acceptance, and we review that evidence.

Open invoices and payment history

Invoices open in Stripe on your cutover date stay in Stripe and collect there. Past invoices and payments stay in Stripe too. Keep your Stripe account open so you can still read them and issue refunds on them.

What a customer may be asked to do

StepWhen the customer sees itWhere
Add a card againOnly when their card didn't come over, for example a card saved with Link or one that has expiredA card setup link, or the payment method component in your app
Authorize ACH debits againOnly when their bank account or authorization didn't come overA bank authorization link, or the payment method component in your app

Every other customer keeps paying the way they do today.

Before you start

  • A ServiceAccount and API key for your platform. See Environments and authentication.
  • Your Account set up to collect invoice payments. We turn on card and ACH collection for your Account during onboarding.
  • Your Stripe customer list, with each customer's default payment method.
  • A cutover date, and a few weeks before it for the card export. Stripe's migrations team sets the timing of the transfer.

Step 1: Create a Payer for each customer

Create one Payer per customer with POST /v3/payments/payers. Use the Stripe customer ID as externalId, because the card export matches on it.

curl -X POST https://api.wingspan.app/v3/payments/payers \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "externalId": "cus_Q4b8Tn2Lx7Rw",
    "profile": { "email": "[email protected]", "displayName": "Northwind Clinic" },
    "metadata": { "customerId": "cust-7731", "region": "west" }
  }'

Find a Payer later with GET /v3/payments/payers?filter[externalId][eq]=cus_Q4b8Tn2Lx7Rw. A second create with the same externalId returns 409, so a re-run won't duplicate a Payer.

For a customer billed at several locations, add a PayerEngagement for each location with POST /v3/payments/payers/{payerId}/engagements. Invoices then name the location's payerEngagementId.

Step 2: Move saved cards with Stripe's card data export

  1. Tell your Wingspan team you're moving saved cards. We send you the receiving processor's attestation of compliance and PGP public key.
  2. Contact Stripe's migrations team and request a payment data export to that processor.
  3. Stripe sends the encrypted file directly to the processor.
  4. We attach each card to the Payer whose externalId is the Stripe customer ID, and set the customer's default card in Stripe as their default for automatic collection.
  5. Check the result for each Payer with GET /v3/payments/payers/{payerId}/payment-methods, and compare the counts with your Stripe customer list.

Start this early. The transfer runs on Stripe's schedule, and the cards need to be on file before your first Wingspan invoice comes due.

Step 3: Carry over bank accounts and ACH authorizations

For each customer who pays by ACH debit, we need the bank account and the customer's authorization.

We create the bank accounts from Stripe's export on the matching Payers. If you hold the bank details yourself, create the account with POST /v3/finance/external-bank-accounts and subject set to { "type": "Payer", "id": "<payerId>" }.

Then record the customer's existing authorization as a mandate. Build a PDF of what the customer agreed to: your authorization text, the date they accepted it, and the acceptance record from their Stripe Mandate. Upload it with POST /v3/compliance/vault-files, then call POST /v3/payments/mandates.

curl -X POST https://api.wingspan.app/v3/payments/mandates \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "subject": { "type": "Payer", "id": "Pq7Lm2Xv9RtK4sWb8NcY3d" },
    "externalBankAccountId": "Fr8Nq1Lx5Tb3Wz7Km2Vp9d",
    "authorizationType": "Recurring",
    "evidenceType": "SignedDocument",
    "evidenceFileId": "Jv1Qz6Kn3Xw8Lt5Rb2Mp7c",
    "authorizedBy": "Dana Ortiz",
    "termsVersion": "ach-debit-2024-06",
    "amountRule": { "type": "Maximum", "amount": 25000.00, "currency": "USD" }
  }'

Set amountRule to the largest single debit the customer authorized. We review the evidence on each signed-document mandate, and debits run under tighter limits until we approve it. Record your mandates a few weeks before cutover so the reviews finish before the first due date. The field reference is in Collect payment on an invoice.

Step 4: Turn on automatic collection

Set each customer's saved card, or bank account and mandate, as the default for their engagement with PATCH /v3/payments/payers/{payerId}/engagements/{payerEngagementId}:

{
  "fundingAuthorization": {
    "mode": "Override",
    "fundingSource": { "type": "ExternalBankAccount", "id": "Fr8Nq1Lx5Tb3Wz7Km2Vp9d" },
    "mandateId": "Gt5Lw9Xq2Rn6Vb1Zk4Mp8s"
  }
}

For a card, set fundingSource to { "type": "PaymentCard", "id": "<paymentCardId>" } and leave out mandateId. Then turn on collection for the customer with PATCH /v3/payments/payers/{payerId} and { "isAutomaticInvoiceCollectionEnabled": true }. To turn it on or off for one location only, set automaticCollectionMode on that location's engagement. We charge the default method on or after each invoice's due date.

Step 5: Invoice in Wingspan from your cutover date

From your cutover date, create new invoices with POST /v3/payments/invoices, then open them with POST /v3/payments/invoices/{invoiceId}/send.

curl -X POST https://api.wingspan.app/v3/payments/invoices \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "payerId": "Pq7Lm2Xv9RtK4sWb8NcY3d",
    "externalId": "inv-2026-11-0412",
    "currency": "USD",
    "dueDate": "2026-12-05",
    "creditFeeHandling": { "payerFeePercentage": 100, "payeeFeePercentage": 0 },
    "notificationPreferences": { "shouldSendInvoice": false, "shouldSendReceipt": false, "shouldSendReminders": false },
    "lineItems": [
      { "description": "Shift on Nov 2", "lineItemType": "Services", "quantity": 8, "unitCost": 95, "metadata": { "jobId": "job-88213" } }
    ]
  }'

creditFeeHandling decides who pays the card fee. If you leave it out, your Account pays all of it. Leave notificationPreferences out if you want us to email the invoice, receipt and reminders. An invoice that's open in Stripe on your cutover date stays in Stripe. If you ever need to move one, void it in Stripe first, so you don't charge the customer in both places.

Step 6: Ask the remaining customers to update their payment details

For a customer whose card didn't come over, create a card setup with POST /v3/payments/payer-card-setups and email them the link with POST /v3/payments/payer-card-setups/{paymentCardId}/send. For a customer whose bank authorization didn't come over, send a mandate request. Both links work without a Wingspan login.

If your customers sign in to your app, the ManagePaymentMethod component in the Embedded SDK lets them add a card or bank account there instead.

Track payments

Subscribe with POST /v3/platform/webhooks to:

EventUse it to
Invoice.PaymentInTransitShow that a payment has started
Invoice.PaidMark the invoice paid in your system
Invoice.DepositConfirmedConfirm the funds reached your bank account
Invoice.PaymentFailed, Invoice.ReturnedFollow up with the customer
Mandate.Activated, Mandate.RevokedKeep each customer's ACH authorization current
Mandate.EvidenceApproved, Mandate.EvidenceRejectedTrack the review of the authorizations you recorded

Paid vs DepositConfirmed explains the difference between the two paid states.

Common mistakes

  • Matching Payers by email. A create with an email we already have can return the existing Payer unchanged. Match on externalId.
  • Creating Payers with your own customer ID while the export carries Stripe's. Use the ID the export carries, or keep both and tell your Wingspan team which one to match.
  • Requesting the card export late. Stripe's migrations team needs time, and we can't charge customers automatically until they have a card on file.
  • Leaving creditFeeHandling out when your customers should pay the card fee.
  • Sending Wingspan's invoice emails on top of your own. Set notificationPreferences on every invoice.
  • Moving an invoice that's open in Stripe without voiding it there first.

Related pages


Did this page help you?