Stripe
Stripe lets Opentra (a) see when invoices are paid and (b) raise and send Stripe invoices for orgs you bill through Stripe.
Opentra needs three things from Stripe:
| Credential | Starts with | Used for |
|---|---|---|
| Secret / restricted API key | sk_… or rk_… |
Talking to Stripe (customers, invoices) |
| Webhook signing secret | whsec_… |
Verifying that payment events really came from Stripe |
| Publishable key (optional) | pk_… |
Not required — future client-side use |
You paste these into Settings → Integrations → Stripe.
How Opentra learns an invoice is paid
Section titled “How Opentra learns an invoice is paid”There are two paths, and understanding them is what stops payments double-counting.
Path A — client pays through Xero’s own Stripe gateway. The invoice is raised in Xero, the client clicks Pay Now and pays by card, and Xero marks its own invoice paid. Opentra’s Stripe webhook deliberately ignores these events (they carry no Opentra invoice metadata); instead the hourly Xero status sync notices the Xero invoice is paid and flips the Opentra invoice to Paid. This path needs the Xero integration connected and the worker running — the Stripe webhook is not involved.
Path B — Opentra raises the Stripe invoice.
When a billing run is finalised for an org set to Payment method → Stripe, Opentra creates, finalises and sends a Stripe invoice tagged with its own metadata. When the client pays, Stripe fires invoice.payment_succeeded with that metadata → the webhook marks the Opentra invoice Paid / via Stripe and (if a clearing account is set) posts the payment back into Xero.
Does an org’s “Payment method” setting decide whether payments are recorded?
Section titled “Does an org’s “Payment method” setting decide whether payments are recorded?”No. Each org has a Payment method setting (default Xero) that controls only whether Opentra also raises its own Stripe invoice at finalise. It does not gate payment detection — that’s driven by how the client actually pays:
| Scenario | Recorded as Paid? | By what |
|---|---|---|
| Invoiced in Xero → client uses the Xero Pay Now (Stripe gateway) | ✅ Yes | Hourly Xero sync — for every org |
| Org set to Stripe → Opentra raises the Stripe invoice → client pays it | ✅ Yes | Stripe webhook (+ optional Xero write-back) |
| Client pays a standalone Stripe link never tied to the invoice | ❌ No | Reconcile manually |
The hourly Xero sync is a universal safety net. You only need to set an org to Stripe if you want Opentra, rather than Xero, to generate and send the invoice.
Part 1 — Create the API key
Section titled “Part 1 — Create the API key”A restricted key is recommended (least privilege); a standard secret key is quicker for a Test-mode trial.
Restricted key (recommended):
-
In the Stripe Dashboard, confirm Test mode is on for your first pass.
-
Go to Developers → API keys → Restricted keys → Create restricted key.
-
Name it e.g.
Opentra PSA, and set these permissions (everything else None):Resource Permission Customers Write Invoices Write Invoice Items Write Balance Read Charges Read PaymentIntents Read -
Create key, then Reveal and copy the value (
rk_test_…) — Stripe shows it only once.
Standard key (quickest): under Developers → API keys → Standard keys, reveal and copy the Secret key (sk_test_…). Either key type works in Opentra’s “Secret key” field.
Part 2 — Create the webhook
Section titled “Part 2 — Create the webhook”- In Opentra, open Settings → Integrations → Stripe and copy the webhook endpoint URL it displays (e.g.
https://psa.yourshop.io/api/webhooks/stripe). - In Stripe, go to Developers → Webhooks → Add endpoint.
- Paste the URL, describe it
Opentra PSA, and select exactly these four events:invoice.payment_succeededinvoice.paidpayment_intent.succeededcheckout.session.completed
- Save, then Reveal the endpoint’s Signing secret and copy it (
whsec_…).
Part 3 — Paste into Opentra
Section titled “Part 3 — Paste into Opentra”In Settings → Integrations → Stripe (Admin or Manager), fill in the Secret key, Webhook signing secret, optional Publishable key, and optional Xero clearing account code, then Connect Stripe. Opentra validates the key against Stripe before turning the badge green.
Xero clearing account code (optional)
Section titled “Xero clearing account code (optional)”Only relevant if an org is billed via Stripe and you want the payment to appear in Xero. When set, Opentra posts the Stripe payment against this Xero account (e.g. a “Stripe Clearing” account) so your books stay complete. The code is the Xero account code (e.g. 090) from Accounting → Chart of accounts. Payments can only post to a Xero invoice that is Authorised (not Draft). Leave it blank to mark invoices paid in Opentra without writing to Xero.
Part 4 — Verify (Test mode)
Section titled “Part 4 — Verify (Test mode)”- Dashboard test: Developers → Webhooks → your endpoint → Send test webhook, choose
invoice.payment_succeeded. An HTTP 200 confirms the secret and URL — Opentra logs “ignored: no metadata” for a bare test event, which is expected. - End-to-end: set a test org’s Payment method to Stripe, finalise a billing run for it, then pay the resulting Stripe invoice with test card
4242 4242 4242 4242. The Opentra invoice should flip to Paid / via Stripe within moments.
Part 5 — Go live
Section titled “Part 5 — Go live”Repeat Parts 1–3 in Live mode: create a live restricted key (rk_live_…) with the same permissions, a live webhook (same URL, same four events) with its own live signing secret, and paste the live values in.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause |
|---|---|
| “Stripe rejected the key” when connecting | Wrong key, or a restricted key missing Balance: Read. |
| Webhook attempts show 400 | Signing secret doesn’t match the endpoint (test vs live mix-up), or pasted with whitespace. |
| Webhook shows 200 but invoice never marks paid | The paid invoice wasn’t one Opentra raised. Payments via Xero’s own Stripe gateway are handled by the hourly Xero sync instead. |
| Paid in Stripe but not in Xero | No Xero clearing account code set, or the Xero invoice isn’t Authorised. |