Integrations Guide

Storage, payments, email, SMS, analytics, chat and captcha are add-ons, configured in Admin → Settings → Channels.

Where credentials live

Not in environment variables. In the integration_connections table, encrypted, keyed on SECRET_KEY (falling back to JWT_SECRET).

That has three consequences worth knowing:

  1. Credentials travel with a database dump. Restore a backup elsewhere and the integrations come with it.
  2. Two deployments sharing a JWT_SECRET can read each other's stored credentials; two that do not, cannot. Restoring a dump into a deployment with a different secret leaves the credentials unreadable — reconnect them in the admin.
  3. An integration is a settings change, not a redeploy.

How a channel becomes live

A provider works when it has both:

  • an active connection in Settings → Channels, and
  • a registered driver in the code.

Activating a gateway with no driver would otherwise render a tile that cannot charge anything, so the system simply does not offer it.

Storage — required for photos

Student, staff, guardian and applicant photos all upload through the server image pipeline to object storage. Until a provider is connected, every upload returns "Storage is not configured". It is a setting, not a bug.

Provider Notes
Cloudflare R2 Endpoint is https://<account>.r2.cloudflarestorage.com; public URL is the r2.dev domain or your own
Amazon S3 Region only; no custom endpoint
DigitalOcean Spaces Region drives the endpoint
Google Cloud Storage Through the S3-compatible API

All four speak the S3 API, so one client covers them — only the endpoint, region and public-URL convention differ.

Uploads are resized server-side into WebP variants (a student photo becomes 256×256 and 96×96) and the original is dropped. Sizes are configurable under Theme → Images.

SignatureDoesNotMatch on upload means the stored secret is wrong, or the token was rotated. Re-enter the credentials; it is configuration, not code.

Payments — what parents can pay with

Twenty gateways ship as add-ons: Stripe, Razorpay, PayPal, PayU, Paystack, Flutterwave, Mollie, Square, Checkout.com, Airwallex, Authorize.Net, Mercado Pago, dLocal, Xendit, Tap, Telr, PayTabs, Viva Wallet, eWAY, Yoco, plus Coinbase Commerce, and offline Bank Transfer.

Whatever you connect becomes what parents see. Connect none and the app tells families to pay at the office.

Each gateway completes one of four ways, and the app is told which:

Kind Parent experience
redirect Sent to the provider's hosted page and back
modal The provider's widget opens in the app
form_post A form is posted to the provider
offline Bank details shown; the office confirms receipt

Three guarantees, regardless of gateway: the amount is computed from the invoice and never read from the request; the ledger is untouched until the payment is confirmed; and confirming twice returns the original receipt rather than crediting twice.

Adding a gateway that is not bundled

Implement the driver contract and register it as a server adapter:

TS
export const myDriver: PaymentDriver = {
  provider: "mygateway",
  label: "My Gateway",
  kind: "redirect",
  method: (cfg) => ({ provider: "mygateway", label: "My Gateway", kind: "redirect" }),
  create: async (ctx) => ({ kind: "redirect", redirectUrl: "…", ref: "…" }),
  verify: async (ctx) => ({ paid: true, ref: "…" }),
};

method() builds the parent-facing tile from the decrypted config and must never include a secret. create() receives the server-computed amount. verify() answers only whether the money is real — it does not decide how much.

See Add-on Development.

Email

SMTP, or Postmark, SendGrid or SES as add-ons. Used for OTP codes, invoices, circular digests and notifications. Templates are editable under Settings → Notifications.

If email is not configured in development, OTP codes are printed to the server log so the flow is testable. This never happens in production — that would write a live credential into a log.

SMS and push

SMS delivers OTP codes and absence alerts to families who have a phone but no email — which in many schools is most of them. Push uses FCM tokens registered by the apps at login and cleared at logout.

Recipients choose their own channels; guardians and staff each have email, SMS and push preferences.

Analytics, chat and captcha

Google Tag Manager, Meta Pixel, PostHog, Clarity, TikTok · Crisp, Intercom, Tawk, Tidio, Freshchat, Zendesk · reCAPTCHA v2 and v3 for public forms.

Troubleshooting

Symptom Cause
"Storage is not configured" No active Storage connection in the database this deployment reads
Upload 502 SignatureDoesNotMatch Wrong or rotated storage secret
No online payment option in the app No active Payments connection, or the connected provider has no driver
Payment completed but invoice unpaid The app never called verify, or the gateway did not confirm. Check the attempt's status
OTP never arrives Email/SMS channel not connected; in development, check the server log

A recurring one deserves its own line: if you have more than one database (a staging and a production, say), connecting an add-on updates only the one the admin you used is pointed at. The other keeps saying "not configured".