How to Set Up Payment Processing APIs That Comply With HIPAA

A technical guide to integrating payment APIs in healthcare while maintaining HIPAA compliance, covering tokenization, BAAs, and secure data handling.

CW
Claire Whitfield

Technical Writer · · 3 min read

You're building a payment integration for your clinic. You've picked a processor, you're reading the API docs, and you're wondering how to keep it HIPAA compliant.

Short answer: never store card numbers, keep clinical information out of the payment flow, and know what your processor will and won't cover. The real work is in how you design the data flow.

Why Payments Need Special Handling

Card data is governed by PCI DSS, not HIPAA. The HIPAA question appears when payment records connect to patient identities and care: a copay record that links a patient to an appointment date, a provider, and a service.

HIPAA includes an exemption (section 1179) for the core work of processing payments, which is why many processors, Stripe among them, don't sign Business Associate Agreements. That exemption only covers what's needed to move money. Anything clinical you push into the processor is your responsibility.

Know Where Your Processor Stands

Before writing code, ask your processor whether it signs a BAA and read its healthcare guidance. Either way, design as if the processor should only ever see what's needed to charge a card: an amount, a neutral description, and internal IDs.

Tokenization: Never Store Card Data

Your system should never see or store card numbers. The patient enters card details in the processor's hosted form or client-side library, and your backend receives a token, not the card.

Example using Stripe:

// Client-side: collect card, get token
const { token } = await stripe.createToken(cardElement);
// Send token.id to your backend

// Server-side: charge the card
const charge = await stripe.charges.create({
 amount: 5000,
 currency: 'usd',
 source: token.id,
 description: 'Patient copay'
});

The card never touches your server, which keeps you out of PCI scope.

Keep Clinical Details Out of Payment Metadata

Bad:

stripe.charges.create({
 amount: 15000,
 description: 'Annual physical exam - Type 2 diabetes follow-up'
});

That description is PHI, now stored with your processor.

Good:

stripe.charges.create({
 amount: 15000,
 description: 'Office visit - Invoice #12345',
 metadata: {
 invoice_id: '12345',
 patient_id: 'pat_abc123'
 }
});

Use neutral wording and internal IDs. Clinical details stay in your EHR or practice management system.

Secure the Connection

Use TLS 1.2 or higher for API calls, store tokens and patient IDs in encrypted columns, and verify webhook signatures before processing payment events, so spoofed confirmations never enter your system:

const sig = request.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(
 request.body,
 sig,
 webhookSecret
);

Log Every Payment Event

Log payments initiated, succeeded, and failed, refunds, card updates, and deleted tokens, with timestamps, user IDs, and patient identifiers, in an append-only store or a SIEM. Never log full card numbers or CVVs; the last four digits are enough.

Trigger Workflows Without Exposing PHI

After a payment you'll want to send a receipt, update the appointment, or mark an invoice paid. Use webhooks, and keep the event payload to IDs. Your backend looks up the details in its own database.

With Formisoft's online payments, collection happens inside your forms and booking, and Formisoft webhooks can notify your other systems when a submission comes in.

Test in Sandbox Mode

Test with fake cards, trigger webhooks manually, and cover the edge cases: duplicate charges, timeouts, expired cards, insufficient funds. Error messages should help the patient without leaking PHI: "Payment declined", never "Payment declined for diabetes consultation".

When Something Goes Wrong

Declines: alert the front desk and send the patient a generic message; don't name the provider or the visit.

API errors: retry with exponential backoff, but check the charge status first so you never double-charge.

Missed webhooks: processors retry automatically; poll the API to catch up rather than assuming nothing happened.

Getting Started

  1. Confirm your processor's healthcare position and BAA availability.
  2. Use tokenization; never handle raw card data.
  3. Review every field you send to the processor and remove anything clinical.
  4. Set up logging that gives visibility without exposing sensitive data.
  5. Test thoroughly in the sandbox.

After launch, treat the payment integration like any system that touches patient data: review access, keep encryption current, and revisit your agreements.

Connect intake to the rest of your front desk.

  • Forms, consents, and signatures before the visit
  • Your paper forms converted by AI
  • BAA included
Get started

Connect intake to the rest of your front desk.

Patients finish forms, consents, and insurance cards from their phone. Everything included, set up for you.

BAA included · We set up your front desk