Payment Integration Contract

What your website's Stripe or PayPal donation flow must produce so NP Ledger tracks every gift accurately — including recurring.

Updated July 5, 2026

You do not need any of this for your books to be correct. NP Ledger is a fund-accounting ledger, not a payment host. Every donation can be entered by hand or brought in from a bank import, and the books balance either way. Connecting Stripe or PayPal is an optional layer that saves you the hand-entry and adds richer donor detail — it never becomes a requirement for a correct ledger.

So think of this contract as: if you want NP Ledger to book gifts automatically, here is what the payment side has to send. Turn the integration off and nothing breaks — you just go back to entering gifts yourself.

Two tiers, kept separate on purpose:

  • Tier 0 — always works, no integration. Record a gift in Quick Entry, or import it from your bank feed. Complete, correct books with zero webhooks connected.
  • Tier 1 — optional automation. Point a Stripe or PayPal webhook at NP Ledger and gifts book themselves, with verified donor identity, processing fees, and recurring renewals tracked automatically.

Everything below is Tier 1.

Your website never talks to NP Ledger directly. It only creates the right objects in your own Stripe or PayPal account — the money lands in your account, not ours. You then point that processor's webhook at your NP Ledger endpoint, and NP Ledger books what the webhook reports.

Webhooks — not the browser redirect after checkout — are the source of truth. A donor closing the tab before the "thank you" page still gets booked, because the webhook still fires.

Your donation flow must:

  • Create a Checkout Session (hosted or embedded Checkout) — not a raw PaymentIntent or a standalone Payment Element. NP Ledger books checkout.session.completed; a bare payment_intent.succeeded is ignored.
  • Set the mode by gift type: one-time → mode=payment; recurring → mode=subscription with a recurring Price.
  • Pass the fund designation as metadata. Put it on the Checkout Session metadata, and for subscriptions also set it on subscription_data.metadata so it rides onto every renewal invoice. Stripe does not copy Session metadata onto the subscription for you — miss this and renewals fall back to your default fund. Use the key(s) your NP Ledger fund-allocation rules match.
  • Collect email and billing address so donor identity flows through.

Point your Stripe webhook at your per-org endpoint (/webhooks/stripe/<your-token>/, shown in NP Ledger under Settings → Integrations) and subscribe to:

Event Why
checkout.session.completed Books the gift (and the first charge of a subscription).
invoice.payment_succeeded Books each recurring renewal.
invoice.payment_failed Flags a failed renewal (records a signal; books nothing).
customer.subscription.deleted Flags a cancelled subscription (records a signal; books nothing).
charge.refunded Books a refund.
charge.dispute.funds_withdrawn Books a chargeback.

The first two are required for gifts to book. The failed/cancelled pair is optional — it gives you an early heads-up when a recurring supporter lapses, and it reconciles with NP Ledger's existing lapsed-supporter reporting rather than raising a second alarm. Skip them and your books stay correct; you just lose the early warning.

Your donation flow must:

  • Use the right product per gift type: one-time → standard Checkout (Orders / Capture); recurring → PayPal Subscriptions (a billing plan).
  • Pass the fund designation via custom_id (one-time) or the subscription's custom field (recurring) so it surfaces on the webhook.

Register your PayPal webhook at your per-org endpoint (/webhooks/paypal/<your-token>/). NP Ledger can provision this for you from Settings → Integrations; it subscribes to the capture, sale, and BILLING.SUBSCRIPTION.* events it handles. Money lands in your own PayPal Business account.

Event Why
PAYMENT.CAPTURE.COMPLETED Books a one-time gift.
PAYMENT.SALE.COMPLETED Books each recurring subscription cycle.
PAYMENT.CAPTURE.REFUNDED / PAYMENT.SALE.REFUNDED Books a refund.
PAYMENT.CAPTURE.REVERSED / PAYMENT.SALE.REVERSED Books a reversal.
BILLING.SUBSCRIPTION.CANCELLED / SUSPENDED / PAYMENT.FAILED Flags a lapsed subscription (records a signal; books nothing).

As with Stripe, the money events are what book gifts; the BILLING.SUBSCRIPTION.* events are the optional early-warning layer.

  • Every charge carries a stable, unique id — Stripe's PaymentIntent or invoice id, PayPal's capture or sale id. NP Ledger deduplicates on it, so a webhook redelivery is safe and never double-books. Don't re-fire the same charge under a new id.
  • Fund designation travels with the charge. For recurring gifts it must live on the subscription, not only the initial session, so it survives every renewal.
  • Lifecycle events move no money, so they book no ledger entry. A failed payment or a cancellation records a signal against the gift and feeds your existing lapsed-supporter reporting — it never posts a transaction, and it never raises a duplicate alert.

That's a fine choice, and a common one. Enter gifts in Quick Entry as they come in, or connect a bank feed and import them — your books will be complete either way. Come back to this contract whenever automating the handoff is worth it to you.

Was this page helpful?

Ready to try NP Ledger?

Native fund accounting, Form 990 support, and smarter bookkeeping for nonprofits.