Integrations Overview
This page deepens §16 of the canonical architecture into an integration-by-integration specification. It does not repeat the hosting or tenancy comparisons; link back to the architecture document for those.
Principles
- Provider interfaces, not provider code in modules. Each module calls a port (
FaceProvider,MapsProvider,SmsProvider,BankFileAdapterand so on). Adapters live inapps/api/src/integrations/*and are selected per tenant through the connector framework (below). - Outbox first. Anything leaving the system goes through the transactional outbox and a BullMQ job, so a provider outage delays work and never loses it (system design).
- Files over portal automation for government systems. We generate the file the portal accepts; a human uploads it. Scraping or automating EPFO/ESIC/TRACES portals is legally grey and breaks every time a portal changes.
- India residency. Prefer providers that process in India. Where a provider processes outside India (for example the Expo push relay or Meta WhatsApp), we send the minimum payload and record it in the processor register (privacy).
- Metered and entitled. Every paid integration has an entitlement key in the control plane and emits usage events for billing (architecture §14).
Summary
| # | Integration | Pattern | Direction | Release | Plan |
|---|---|---|---|---|---|
| 1 | Biometric devices | CSV import → push gateway (ADMS-style) → sync agent | Inbound | R1 (CSV/API), R2 (gateway, agent) | Standard+ |
| 2 | Face verification + liveness | Provider port, synchronous | Outbound | R2 | Add-on |
| 3 | Maps, geocoding, snap-to-road | Provider port, cached | Outbound | R1 (geocode), R3 (tracking, kms) | All / FFM add-on |
| 4 | Email (SES) | NTF channel adapter, async | Outbound | R1 | All |
| 5 | SMS / WhatsApp | NTF channel adapter, async, DLT templates | Outbound | R1 (SMS), R1–R2 (WhatsApp) | Metered |
| 6 | Push (Expo / FCM / APNs) | NTF channel adapter, async | Outbound | R1 | All |
| 7 | Statutory files (ECR, ESIC, PT, LWF, 24Q, Form 16) | File generation | Export | R1 (ECR/ESIC/PT/LWF), R2 (24Q, Form 16) | Standard+ |
| 8 | Bank payout files and H2H | File adapters → SFTP/API H2H | Export | R1 (files), R4 (H2H) | Standard+ / Enterprise |
| 9 | Accounting (Tally, Zoho Books) | Journal export → API push | Export | R2 (Tally XML, CSV), R4 (Zoho Books API) | Professional+ |
| 10 | SSO | OIDC, SAML 2.0 | Inbound auth | R1 (OIDC), R2 (SAML) | Standard+ / Enterprise |
| 11 | Calendar | One-way event sync | Outbound | R3 | Professional+ |
| 12 | Payments and billing (Razorpay) | Control-plane only, webhooks | Both | R0/R1 | Platform |
| 13 | Public API and webhooks | REST /v1, OAuth2, signed webhooks | Both | R2 | Professional+ |
| 14 | DigiLocker / PAN verification | Aggregator API, optional | Outbound | R3 (Could) | Add-on |
The release column follows CONVENTIONS §7 and architecture §16. Where they differ, we split the integration: accounting file export ships in R2, while API-based accounting and ERP connectors ship in R4 with the other R4 integrations.
1. Biometric attendance devices
Purpose. Most Indian SME factories, warehouses and offices already own fingerprint or face terminals (ZKTeco, eSSL, Realtime, Matrix, Mantra and similar). We must ingest their punches without forcing a hardware change.
Three tiers, cheapest to support first
| Tier | How it works | When to use | Release |
|---|---|---|---|
| CSV / Excel import | The admin exports the device log (or a vendor attendance software export) and uploads it. A column-mapping wizard saves the mapping per tenant | Any device, no network access, pilot clients | R1 |
| Punch API | POST /v1/attendance/punches (batch, idempotent by device_serial + device_user_id + punched_at) for clients or partners with existing middleware | Clients with a vendor middleware such as eTimeTrackLite exporting to SQL | R1 |
| Push gateway (ADMS-style) | The device is configured with our server address and pushes punches over HTTP(S). The ingest process speaks the device's push protocol, authenticates the serial, and enqueues punches | ZKTeco/eSSL devices with ADMS/"cloud server" firmware | R2 |
| Sync agent | A small signed agent (Windows service or Linux container) on the client LAN polls devices through the vendor SDK or TCP protocol and forwards punches outbound over HTTPS | Older devices without push firmware; sites where devices cannot reach the internet | R2 |
Data exchanged. Inbound only: device serial, device user ID (mapped to an employee through att.device_user_map), timestamp in device local time, verify mode (finger/face/card/PIN), in/out state if the device has one, and work code. Outbound to the device (push tier only): acknowledgements, time sync, and optionally a user list for enrolment. We never pull fingerprint templates from devices. Templates stay on the device.
Auth.
- Push gateway: device serial registered in Spyrl to a tenant and a location, plus a per-device secret in the URL path or a header where the firmware supports it (
https://ingest.spyrl.app/devices/<serial>). ADMS firmware is weak at authentication, so we add compensating controls: unknown serials are quarantined, not accepted; each serial can only post from the IP ranges the tenant registered (optional); and rate limits are applied per serial. - Sync agent: enrolled with a one-time code, which it exchanges for a client certificate or a long-lived device token scoped to
att.punch.ingestfor one tenant. The agent connects outbound only, so no inbound firewall rule is needed at the client site.
Failure handling.
- Devices buffer punches locally and re-send. Ingest is idempotent on the natural key, so duplicates are harmless.
- Clock drift: the gateway compares the device time to server time on each heartbeat. A drift above 2 minutes raises a device-health warning; above 10 minutes the punches are flagged for review.
- Late punches (a device offline for three days) trigger a recompute of the affected attendance days through the deterministic day computer. If the payroll period is already locked, the punches land in an exceptions queue for HR.
- Device health: last-seen heartbeat per device. No heartbeat for 2 hours during a shift window sends an alert to the tenant's HR admin (not to Spyrl on-call).
Provider options.
| Option | Pros | Cons | Verdict |
|---|---|---|---|
| Implement the ADMS-style push protocol ourselves | No middleman, no per-device fee, works with most ZKTeco/eSSL firmware | Protocol is vendor-documented unevenly; firmware variants differ; we must test each model | Recommended for R2, starting with the two or three models our pilots own |
| Third-party device cloud (vendor middleware with an API) | Faster coverage of many models | Per-device fees, another processor holding attendance data, often hosted outside our control | Accept as a source through the Punch API, not as our core path |
| Sync agent with vendor SDK | Works with old devices and closed networks | We ship and support software on client machines; SDKs are Windows-centric | Recommended as the fallback; keep it small and auto-updating |
| CSV only | Zero engineering | Manual, delayed, error-prone | Always available; never the only option |
2. Face verification with liveness
Purpose. Verify that the person punching is the enrolled employee (mobile self-punch and kiosk mode), and stop photo or screen replay attacks. The reference product enrolls faces but shows no liveness or anti-spoof check (research §21); liveness is a must for us.
Pattern. A synchronous provider port:
interface FaceProvider {
enroll(input: { tenantId: string; employeeId: string; images: Buffer[] }): Promise<{ templateRef: string; quality: number }>;
verify(input: { tenantId: string; templateRef: string; probe: Buffer }): Promise<{ match: boolean; similarity: number }>;
liveness(input: { sessionId: string }): Promise<{ live: boolean; confidence: number; auditImageRef?: string }>;
remove(input: { tenantId: string; templateRef: string }): Promise<void>;
}
Data exchanged. Enrolment images (sent once, then discarded), a probe image or liveness session per punch, and a match score back. We store a template reference or encrypted embedding, never the raw enrolment image, wherever the provider allows. The punch selfie is kept as evidence for 180 days, then purged (architecture §7).
Auth. Server-side calls with IAM roles (AWS) or an API key held in the connector secret store. The mobile app never holds provider credentials; it gets a short-lived liveness session token from our API.
Consent gate. Enrolment is blocked until the employee has an active biometric.face consent record (privacy). Withdrawing consent deletes the template and switches the employee to a fallback method (PIN plus selfie without matching, or a supervisor-witnessed punch).
Failure handling.
- Provider timeout (over 3 s) or outage: the punch is accepted as "unverified" with the selfie and geo evidence, and flagged for manager review. Attendance must never stop because a vendor is down.
- Low similarity (between the reject and accept thresholds): accept with a review flag. Below the reject threshold: reject with a retry; three failures route to a manager.
- Thresholds are per tenant policy, with safe defaults set during the R2 pilot from measured false-accept and false-reject rates.
Provider options.
| Option | Liveness | Residency | Cost (estimate) | Notes |
|---|---|---|---|---|
| AWS Rekognition (CompareFaces/collections) + Rekognition Face Liveness | Yes (managed, video-based) | ap-south-1 (verify Face Liveness regional availability before committing) | Low per verification for CompareFaces; Face Liveness is priced per check and is several times higher | Same cloud and IAM as the rest of the stack; first implementation |
| On-device (ML Kit face detection + an embedding model on device) | Passive liveness only, weaker | Data stays on device | No per-call fee | Best for offline kiosks; needs model maintenance and careful accuracy testing |
| Specialist Indian KYC/face vendors | Strong certified liveness (often iBeta-tested) | India | Per-call fees, usually higher | Consider for Enterprise or if Rekognition accuracy on our pilot population is not good enough |
| Aadhaar face authentication (UIDAI) | Yes | India | Needs AUA/KUA licensing | Out of scope: licensing burden and Aadhaar-use restrictions |
3. Maps, geocoding and snap-to-road
Purpose. Geocode office and client addresses into geo-fences (R1); reverse-geocode punch locations for display; draw field routes and compute travelled kilometres for mileage claims (R3, FFM).
Pattern. MapsProvider port with geocode, reverseGeocode, snapToRoad and distanceMatrix. Results are cached in Redis and Postgres (geocodes for 90 days, reverse geocodes rounded to about 50 m cells). Snap-to-road runs once per completed trip in a worker, not per GPS point.
Data exchanged. Addresses and coordinates. GPS traces are sent to the provider only as anonymous coordinate sequences without employee identifiers.
Auth. Server-side API keys restricted by IP and API. The mobile map tiles use a separate key restricted to the app's package name and signing certificate.
Failure handling. Geocoding failure leaves the address unverified and asks the admin to drop a pin. Snap-to-road failure falls back to a haversine distance with outlier filtering, marked "estimated" on the claim, so a manager can revise it with an audit reason.
Provider options.
| Criterion | Google Maps Platform | Mappls (MapmyIndia) | Self-hosted OSRM + OpenStreetMap |
|---|---|---|---|
| India coverage (addresses, rural roads) | Very good | Very good, strong on Indian addressing (eLoc) | Good in cities, patchy in rural areas |
| Snap-to-road | Roads API | Available (snap-to-road / route APIs) | OSRM match service |
| Pricing (estimate) | Per-SKU pricing with a free monthly allowance per SKU; costs grow fast with tracking volume | INR pricing, typically negotiable for Indian SaaS volume | Infra only (a modest instance with the India extract) |
| Residency | Processed globally | India | Our VPC |
| Familiarity | High; the reference product uses Google Maps | Medium | Low; we run it |
Recommendation. Use Google for R1 geocoding (low volume, best developer experience). Before R3, run a cost model on expected tracked users: if snap-to-road volume exceeds the free tier materially, move snap-to-road to Mappls or to self-hosted OSRM, behind the same port. Keep the provider choice per cell, not per tenant, to avoid support complexity.
4. Email (Amazon SES)
Purpose. Invites, password resets, approvals, payslips, reports, and system notices.
Pattern. NTF email channel adapter, sent from a BullMQ queue with per-tenant concurrency. SES in ap-south-1. Templates are rendered by NTF, not by SES.
Data exchanged. Recipient address, subject and body. Payslips are never attached unprotected. The email carries a link to the ESS app; if the tenant enables attachments, the PDF is password-protected (a pattern such as PAN-prefix plus date of birth, which the employee is told about in onboarding, never the password itself).
Auth. IAM role for the worker. Sending domain mail.spyrl.app with SPF, DKIM and DMARC (p=quarantine at launch, p=reject after 60 days clean). Enterprise tenants can use their own domain after DKIM verification.
Failure handling. SES bounce and complaint events arrive through SNS → ntf.delivery.updated. Hard bounces add the address to a per-tenant suppression list and show "email undeliverable" on the employee profile. Throttling errors back off exponentially; the queue retries for 24 hours, then lands in the DLQ with an alert.
Options. SES is cheapest and in-region. Postmark or SendGrid give better deliverability tooling but process outside India; not chosen.
5. SMS and WhatsApp
Purpose. Invite activation and OTPs where employees lack email (common for shop-floor staff), approval nudges, and payslip-ready notices. WhatsApp is often the most effective channel for Indian blue-collar workforces.
Pattern. NTF channel adapters SmsProvider and WhatsAppProvider, async through BullMQ, with metering per message.
Regulatory constraint (SMS). TRAI DLT registration is mandatory: Spyrl registers as the principal entity, registers sender headers (for example SPYRLH) and every template with variables. Tenants cannot send free-text SMS. Each NTF template that can go by SMS stores its DLT template ID.
WhatsApp. Messages go through the Meta WhatsApp Cloud API either directly or through a BSP (Gupshup, MSG91 and others). Business-initiated messages need pre-approved templates (utility or authentication category). Opt-in is captured per employee and stored with the consent records.
Data exchanged. Phone number, template ID and variables (name, link, OTP). Never salary figures, PAN or Aadhaar in SMS or WhatsApp bodies.
Auth. API keys in the connector secret store; webhook callbacks for delivery status verified by signature or a secret path.
Failure handling. Delivery receipts update ntf_delivery. Failover order is per message type: OTP goes WhatsApp → SMS after 30 s with no delivery; nudges do not fail over (cost). The provider is switched by configuration when one has a regional outage.
Provider options.
| Criterion | MSG91 | Gupshup | Meta Cloud API direct |
|---|---|---|---|
| SMS (DLT) | Yes, strong | Yes | No |
| Yes (BSP) | Yes (large BSP) | Yes | |
| One vendor for both | Yes | Yes | No |
| Pricing (estimate) | SMS roughly ₹0.15–0.25 per message; WhatsApp at Meta's per-message rates plus a BSP margin | Similar | Meta rates only, but we build template management and webhooks ourselves |
| Recommendation | Primary for SMS and WhatsApp in R1 (one contract, one DLT setup) | Secondary / failover | Revisit at high volume to cut the BSP margin |
Prices change often; treat these as estimates and re-check before each pricing review.
6. Push notifications (Expo, FCM, APNs)
Purpose. Approval requests, punch reminders, announcements.
Pattern. Expo Push Service from the worker using the Expo push token registered at login. The device token is stored against the session, so revoking a session stops pushes to that device.
Data exchanged. A title, a short body and a deep link. Payloads contain no personal data beyond a first name. The app fetches details after opening, through the authenticated API.
Auth. Expo access token (enhanced push security enabled) in the secret store. FCM and APNs credentials are held in EAS.
Failure handling. DeviceNotRegistered receipts delete the token. Receipts are polled 15 minutes after sending. Push is best-effort: anything that needs action also appears in the in-app inbox.
Options. Expo push is free and simplest with an Expo app. Direct FCM v1 and APNs remove a hop (and a non-India relay) and are the documented exit path if Expo push becomes a constraint.
7. Statutory files
Purpose. Produce the files Indian statutory portals accept, so payroll managers upload instead of retyping. The reference product exposes these only as "reports" (research).
Pattern. Pure generators in packages/payroll-engine/statutory/*, versioned with the statutory pack (reference data with effective dates, published by the platform; see administration). Each generator takes a locked payroll run and produces a file plus a validation report. No portal automation.
| File | Content | Format (verify against the current portal spec) | Release |
|---|---|---|---|
| EPFO ECR | UAN, member name, gross wages, EPF/EPS/EDLI wages, contributions, NCP days, refunds | ECR 2.0 text file with the #~# field separator | R1 |
| ESIC monthly contribution | IP number, IP name, days paid, total wages, reason code for zero days | Excel upload template from the ESIC portal | R1 |
| Professional Tax | Per state: employee count by slab, amount deducted | State-specific challan/return data (CSV/Excel) | R1 |
| LWF | Per state: employee and employer contributions | State-specific | R1 |
| TDS 24Q (quarterly) | Deductor, challans, deductee rows (PAN, amount paid, TDS); Annexure II in Q4 | Text file importable into the NSDL/Protean Return Preparation Utility, then validated by the FVU | R2 |
| Form 16 Part B | Salary breakup, exemptions, deductions, tax computation | PDF per employee, merged with Part A downloaded from TRACES | R2 |
Data exchanged. Generated files are downloaded by users with pay.statutory_file.generate. They contain identifiers (UAN, PAN), so downloads are audited and links expire after 15 minutes.
Failure handling. Pre-generation validation blocks the file and lists errors (missing UAN, invalid PAN format, wages above a ceiling with no override). Each file records the statutory pack version used, so a regenerated file is identical. If the portal rejects a file, the payroll manager records the rejection reason and we add a validation rule in the next pack.
Options. File generation (chosen) vs portal automation (rejected: fragile, CAPTCHA, credential sharing) vs third-party compliance filing partners (possible later as a partner integration through the public API).
8. Bank payout files and host-to-host
Purpose. Pay net salaries, reimbursements and F&F settlements.
Pattern. BankFileAdapter per format, generated from an approved payroll run or payout batch.
| Stage | Mechanism | Release |
|---|---|---|
| Generic | NEFT/RTGS/IMPS bulk CSV with a configurable column template | R1 |
| Bank-specific files | Templates for the major SME banks (for example HDFC, ICICI, Axis, SBI, Kotak corporate-banking bulk upload formats) | R1 (first three requested by pilots) |
| Payout API | RazorpayX Payouts or a similar payout aggregator, from a client-funded account | R4, optional |
| Host-to-host (H2H) | SFTP drop with PGP encryption, or bank corporate APIs, under a tri-party agreement with the client's bank | R4, Enterprise |
Data exchanged. Beneficiary name, account number, IFSC, amount, narration. Account numbers are decrypted only inside the generator job (envelope encryption, see security).
Controls. Segregation of duties: the person who changed an employee's bank details in the last 24 hours cannot generate the file that includes that employee, and a changed account is highlighted in the payout preview. Each file has a checksum and a record count stored in the audit log. Files are encrypted at rest and expire from storage after 30 days.
Failure handling. Bank rejection files (returns) are uploaded or fetched (H2H) and matched to payout lines; failed lines go to an exceptions list for re-payment in the next batch. For H2H, PGP keys rotate yearly and the SFTP endpoint is IP-allow-listed on both sides.
9. Accounting (Tally, Zoho Books)
Purpose. Post the payroll journal (salary expense, statutory liabilities, net pay payable) and reimbursements to the client's books.
Pattern. The payroll engine produces a neutral JournalEntry[] from a locked run, using a per-tenant ledger mapping (component → ledger name, cost centre by department or location). Adapters render it:
| Target | Mechanism | Auth | Release |
|---|---|---|---|
| CSV | Download | — | R2 |
| TallyPrime / Tally.ERP 9 | Downloadable Tally XML (journal vouchers) that the accountant imports. Tally runs on the client's desktop, so there is no cloud push. A desktop bridge that posts to Tally's local XML port is a later option | — | R2 |
| Zoho Books | Zoho Books API (journals), India data centre (zoho.in) | OAuth 2.0 per tenant, refresh token in the secret store | R4 |
| Other ERPs (SAP Business One, Busy, Marg) | Via CSV or the public API through partners | — | Later |
Failure handling. Unmapped components block export with a clear list. API pushes are idempotent by run ID; a partial failure is retried per entry, and the run shows "posted / partially posted / failed".
10. SSO (OIDC and SAML)
Purpose. Let tenants sign in with Google Workspace or Microsoft Entra ID; SAML 2.0 for enterprises with Okta, ADFS or similar.
Pattern. Built into IAM. A tenant admin with iam.sso.manage configures a connection; users are matched by verified email to existing employees only (just-in-time linking, never just-in-time creation of employees). Password login can be disabled per tenant once SSO is verified, with an owner break-glass password plus MFA kept for lockout recovery.
| Protocol | Providers | Plan | Release |
|---|---|---|---|
| OIDC (authorization code + PKCE) | Google Workspace, Microsoft Entra ID | Standard+ | R1 |
| SAML 2.0 (SP-initiated, signed assertions required) | Any IdP | Enterprise | R2 |
| SCIM 2.0 provisioning | Okta, Entra | Enterprise | Later (only on demand) |
Failure handling. IdP outage: users with a password set (if allowed by policy) can still log in; otherwise the login page shows the IdP status and the tenant admin's contact. SAML certificate expiry is tracked and the tenant admin is warned 30, 14 and 3 days ahead.
Options. Build OIDC on a standard library (openid-client); for SAML, either a maintained library (@node-saml/node-saml) or a self-hosted SAML-to-OIDC bridge (for example SAML Jackson). Hosted identity brokers are simpler but add a non-India processor and per-connection fees. Recommendation: library for OIDC in R1; decide library vs self-hosted bridge at the R2 SAML spike.
11. Calendar
Purpose. Show approved leave and holidays on the employee's and team's work calendars.
Pattern. One-way, event-driven: lve.request.approved and lve.request.cancelled create, update or delete an event. No reading of user calendars.
Auth. Google: a Workspace admin grants domain-wide delegation to our service account for the calendar scope only, or per-user OAuth. Microsoft: admin consent for Calendars.ReadWrite via Microsoft Graph application permissions.
Data exchanged. Event title ("On leave"), dates, optional leave type if the tenant allows it. No medical or reason text.
Failure handling. Retries for 24 hours, then the connector health turns amber; leave approval is never blocked by calendar sync. Release R3.
12. Payments and billing (Razorpay)
Purpose. Collect Spyrl subscription fees from tenants. This runs only in the control plane; app cells never touch payment data.
Pattern. Razorpay Subscriptions (UPI AutoPay, cards, e-mandate) for self-serve plans; invoices with payment links for Enterprise annual contracts. The control plane computes the billable quantity from usage_daily (architecture §14), updates the subscription quantity before each cycle, and issues GST invoices.
Auth. Razorpay key ID and secret in AWS Secrets Manager; webhooks verified with the X-Razorpay-Signature HMAC. Webhook handlers are idempotent on the event ID.
Failure handling. payment.failed → past due with a 7-day grace and a banner → suspended (read-only, payroll export still allowed). Never suspend a tenant inside the payroll window (25th to 5th) without senior approval. A nightly reconciliation job compares Razorpay subscriptions with the tenant registry.
13. Public API and webhooks
Purpose. Let clients and partners integrate with their own systems: HR master sync, attendance import, payroll outputs, ERP posting. The contract is detailed in the API overview.
API. REST at https://api.spyrl.app/v1, OpenAPI 3.1 generated from the shared zod contracts. OAuth 2.0 client credentials, one client per tenant integration, with scopes mapped to permissions (for example scope emp.profile.read). Field-level protection applies: sensitive fields need explicit scopes and are masked otherwise. Rate limits per client (default 600 requests per minute, burst 100), with Retry-After. Additive changes only; deprecations are announced 6 months ahead (upgrades).
Webhooks. Subscriptions per event type (<module>.<entity>.<verb-past>, for example emp.employee.joined, lve.request.approved, pay.run.locked; the canonical catalogue is in Events & webhooks). Payloads are "thin" (IDs plus changed fields); the receiver fetches sensitive data through the API.
POST /hooks/spyrl HTTP/1.1
Content-Type: application/json
Spyrl-Event-Id: 0193b3b2-4c1d-7e2f-8a3b-4c5d6e7f8091
Spyrl-Event-Type: lve.request.approved
Spyrl-Signature: t=1761900000,v1=5d41402abc4b2a76b9719d911017c592...
{"id":"0193b3b2-4c1d-7e2f-8a3b-4c5d6e7f8091","type":"lve.request.approved","tenantId":"...","occurredAt":"2026-10-31T09:00:00Z","data":{"requestId":"...","employeeId":"..."}}
- Signature: HMAC SHA-256 over
t + "." + bodywith a per-subscription secret; receivers reject timestamps older than 5 minutes. - Delivery: at least once; retries with exponential backoff and jitter for 24 hours (about 12 attempts), then the DLQ. Admins can replay from the delivery log for 30 days.
- SSRF safety: HTTPS only; DNS is resolved and private, link-local and metadata IP ranges are rejected; no redirects followed; 10-second timeout; egress through a dedicated NAT IP that clients can allow-list.
- Auto-disable after 3 days of continuous failure, with an email to the tenant admin.
14. DigiLocker and PAN verification (optional)
Purpose. Reduce fake documents during onboarding: verify PAN name match, fetch documents from DigiLocker with the employee's consent, verify bank accounts with a penny drop.
Pattern. An IdentityVerificationProvider port backed by a licensed aggregator (several Indian KYC API providers offer PAN, DigiLocker and bank verification). The employee starts the flow in ESS; DigiLocker requires the employee's own consent on the DigiLocker screen.
Data exchanged. PAN and name (PAN verification); a DigiLocker consent redirect and the fetched document (stored as an EMP document); account number and IFSC (penny drop). We do not collect or store full Aadhaar numbers from DigiLocker; where Aadhaar appears we store only a masked form unless a statutory need is documented.
Auth. Aggregator API keys per cell in the secret store; per-call metering billed to the tenant as an add-on.
Failure handling. Verification is advisory: failure marks the document "unverified" and HR verifies manually. Release R3, MoSCoW Could.
Connector framework
All integrations above that hold per-tenant configuration (biometric devices, SMS/WhatsApp overrides, bank formats, accounting, SSO, calendar, webhooks, identity verification) are connectors. One framework gives them the same configuration UI, secret handling, health checks and audit.
Interface
export interface Connector<Config, Secrets> {
key: string; // e.g. 'acct.zoho_books', 'bank.hdfc.bulk_csv', 'att.adms_push'
module: 'ATT' | 'PAY' | 'NTF' | 'IAM' | 'EMP' | 'LVE' | 'EXT';
kind: 'inbound_push' | 'inbound_poll' | 'outbound_api' | 'file_export' | 'auth';
entitlement: string; // control-plane feature key, e.g. 'integration.accounting'
configSchema: z.ZodType<Config>; // non-secret settings, rendered as a form
secretsSchema: z.ZodType<Secrets>; // secret fields, write-only in the UI
validate(ctx: ConnectorContext, cfg: Config, sec: Secrets): Promise<ValidationResult>;
healthCheck(ctx: ConnectorContext): Promise<HealthResult>; // cheap, side-effect free
onEvent?(ctx: ConnectorContext, event: DomainEvent): Promise<void>;
poll?(ctx: ConnectorContext, cursor?: string): Promise<{ items: unknown[]; cursor: string }>;
exportFile?(ctx: ConnectorContext, input: unknown): Promise<GeneratedFile>;
}
export interface HealthResult {
status: 'ok' | 'degraded' | 'failing' | 'unconfigured';
checkedAt: string;
detail?: string; // never contains secrets or personal data
}
Per-tenant configuration
| Table (app plane) | Purpose |
|---|---|
integration_connection | tenant_id, connector_key, status (draft/active/paused/failing), config (jsonb, validated by configSchema), secret_ref, created_by, last_success_at, last_error_at, consecutive_failures |
integration_run | One row per sync, poll or export: counts, duration, outcome, error code |
integration_cursor | Poll cursors and watermarks |
integration_mapping | Key-value mappings (device user → employee, component → ledger) |
Secrets
- Platform-level credentials (SES, MSG91, Rekognition, Razorpay) live in AWS Secrets Manager per environment and cell.
- Tenant-level credentials (Zoho refresh tokens, SAML certificates, webhook signing secrets, bank SFTP keys) are stored envelope-encrypted in Postgres with the tenant's data key (the same KMS-wrapped per-tenant key used for PAN and bank fields). One Secrets Manager secret per tenant connection would cost about $0.40 per secret per month and adds API throttling at scale (estimate), for no real gain.
- Secrets are write-only in the UI and API; reading back shows only the last four characters and the update time. Every write is audited.
Health checks and operations
- A scheduler runs
healthCheckevery 15 minutes for active connections, and immediately after a configuration change. consecutive_failuresof 5 setsfailing, notifies the tenant admin (the tenant owns their connector's config), and pauses outbound pushes to protect the provider quota. Platform-wide provider failures (for example SES throttling in a cell) alert Spyrl on-call instead (observability).- The tenant admin console shows each connection's status, last success, last error, recent runs and a "test connection" button.
- Connector code is versioned with the release train; a connector can be disabled globally with a kill-switch flag (
integration.<key>.enabled).
Adding a connector (checklist)
- Write the adapter behind the existing port; no module code changes.
- Define
configSchema,secretsSchemaand the entitlement key. - Add contract tests against a recorded sandbox or a fake (testing strategy).
- Add the provider to the processor register if it receives personal data (privacy).
- Write the runbook entry: what "failing" means and who acts (tenant admin or Spyrl).