External Services Integration
External Services Integration
Project: Drop Version: 0.1.0 Date: 2026-02-23 Author: Platform Architect (AI) Status: In Review Reviewers: Alem Bašić (CEO)
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | 2026-02-23 | Platform Architect (AI) | Initial draft from source code and services analysis |
1. Overview
Drop integrates with the following external services. Service mode is controlled by NEXT_PUBLIC_SERVICE_MODE env var (mock | production).
| Service | Status | Purpose |
|---|---|---|
| BankID OIDC | PRODUCTION | Norwegian eID authentication — mandatory for all users |
| Sumsub | PRODUCTION | KYC/identity verification — WebSDK + webhook |
| Open Banking (AISP/PISP) | TBD — provider selection pending | Read bank balance, initiate payments |
| Slack | PRODUCTION | Operational alerting via incoming webhook |
| BetterStack | PRODUCTION | External uptime monitoring |
| Swan Open Banking | DEPRECATED | Was planned, no longer selected |
| Stripe Issuing | MOCK (future) | Card issuance — no SDK, no API keys |
Key rule: Sumsub is the ONLY connected external service with real API calls. All other services except BankID and Slack are currently mocked.
2. BankID OIDC — Norwegian eID
2.1 Purpose
BankID is Drop's sole authentication mechanism. All users must authenticate with Norwegian BankID (eID) before accessing any features. This satisfies PSD2 Strong Customer Authentication (SCA) requirements.
2.2 Integration Details
| Parameter | Value |
|---|---|
| Protocol | OIDC (OpenID Connect) |
| Flow | Authorization Code (PKCE) |
| Identity claim | pid — Norwegian national ID (11 digits) |
| Age verification | DOB encoded in pid — must be >= 18 |
| User storage | national_id_hash = SHA-256(pid) — never store raw pid |
| Web callback | BANKID_CALLBACK_URL (e.g., https://getdrop.no/api/auth/bankid/callback) |
| Mobile callback | BANKID_CALLBACK_URL_MOBILE = drop://auth/callback (deep link) |
| JWKS verification | Verified against BankID JWKS endpoint |
2.3 Environment Variables
| Variable | Required | Description |
|---|---|---|
BANKID_CLIENT_ID |
Yes (prod) | BankID OIDC client ID |
BANKID_CLIENT_SECRET |
Yes (prod) | BankID OIDC client secret — stored in Secrets Manager |
BANKID_CALLBACK_URL |
Yes (prod) | Web redirect URI after auth |
BANKID_CALLBACK_URL_MOBILE |
Yes (mobile) | Mobile deep link redirect |
BANKID_AUTHORIZE_URL |
No | Default: BankID production authorize endpoint |
BANKID_TOKEN_URL |
No | Default: BankID production token endpoint |
BANKID_JWKS_URL |
No | Default: BankID production JWKS |
BANKID_ISSUER |
No | Default: BankID production issuer |
BANKID_MOCK |
No | true skips real OIDC (dev/staging only) |
2.4 Mock Mode (BANKID_MOCK=true)
In development and staging, BANKID_MOCK=true enables a simulated BankID flow:
- Skips the full OIDC redirect
- Auto-generates a mock pid and authenticates a test user
- No network calls to BankID
2.5 Error Handling
| Scenario | Handling |
|---|---|
| State mismatch (CSRF) | 400 — reject callback |
| Code exchange failure | 500 — log error, redirect to error page |
| Invalid ID token | 401 — reject |
| User age < 18 | 403 — reject with age error |
| BankID provider down | 503 — user sees error message |
3. Sumsub — KYC / Identity Verification
3.1 Purpose
Sumsub provides KYC (Know Your Customer) identity verification. This is the only production-ready external service with real API calls.
Verification checks:
- Document authenticity (passport, ID card, driver's license, residence permit)
- Liveness check (selfie is a real person)
- Face match (selfie matches document photo)
- Sanctions check (not on international sanctions lists)
- PEP check (not a politically exposed person)
3.2 Integration Architecture
User app (web/mobile)
│
├── 1. GET /api/kyc/initiate → server calls Sumsub createApplicant + getAccessToken
│ returns { token } for WebSDK
│
├── 2. Frontend loads Sumsub WebSDK with token
│ User submits documents + selfie via Sumsub UI
│
└── 3. Sumsub webhook → POST /api/kyc/webhook
Server updates user.kyc_status = 'approved' | 'rejected'
3.3 Key Functions
| Function | Description |
|---|---|
createApplicant({ externalUserId, email?, phone? }) |
Create KYC applicant in Sumsub |
getAccessToken(applicantId) |
Get WebSDK access token (30min TTL) |
getApplicantStatus(applicantId) |
Check current verification status |
getVerificationResult(applicantId) |
Get per-check breakdown |
onWebhook(callback) |
Register webhook listener for status updates |
3.4 Applicant Status Flow
init → pending → queued → completed → (reviewAnswer: GREEN=approved / RED=rejected / RETRY)
| Review Answer | Drop kyc_status |
Meaning |
|---|---|---|
GREEN |
approved |
User verified, can transact |
RED |
rejected |
User rejected, cannot transact |
RETRY |
pending |
Document unreadable, user must resubmit |
3.5 Environment Variables
| Variable | Required | Description |
|---|---|---|
SUMSUB_API_URL |
No | Default: https://api.sumsub.com |
SUMSUB_APP_TOKEN |
Yes (prod) | Sumsub API token — stored in Secrets Manager |
SUMSUB_SECRET_KEY |
Yes (prod) | Webhook signature verification key |
3.6 Mock Mode
When NEXT_PUBLIC_SERVICE_MODE=mock:
- 90% approval rate, 10% rejection
- 3-second simulated delay
- Risk score: 15 (approved) or 85 (rejected)
- Rejected label:
DOCUMENT_UNREADABLE, type:RETRY
3.7 Error Handling
| Scenario | Handling |
|---|---|
| Sumsub API down | Log error, user sees "verification temporarily unavailable" |
| Webhook signature invalid | Reject webhook with 401 |
| Applicant already exists | Reuse existing applicant |
| Document rejected | Set kyc_status=rejected, user can retry with different document |
4. Open Banking — AISP + PISP (Provider TBD)
4.1 Purpose
- AISP (Account Information): Read user's bank balance from their Norwegian bank account
- PISP (Payment Initiation): Initiate transfers directly from user's bank account
This is Drop's core PSD2 architecture — users' funds never move into Drop's control.
4.2 Current State
| Component | Status |
|---|---|
| AISP balance read | Mocked (Swan deprecated) |
| PISP payment initiation | Mocked |
| Real provider | TBD — provider selection pending |
| Swan mock | Deprecated — will be removed |
4.3 Planned Integration Architecture
User initiates payment
│
├── 1. AISP: Refresh balance from bank → update bank_accounts.balance
├── 2. PISP: Initiate payment at provider → returns payment_id
├── 3. User bank sends consent redirect if needed
├── 4. Provider webhook: payment settled → update transaction.status
└── 5. Notify user
4.4 Required Environment Variables (Future)
| Variable | Description |
|---|---|
OPENBANKING_CLIENT_ID |
Provider OAuth client ID |
OPENBANKING_CLIENT_SECRET |
Provider OAuth client secret |
OPENBANKING_CALLBACK_URL |
OAuth redirect URI |
OPENBANKING_WEBHOOK_SECRET |
Webhook signature key |
4.5 Supported Banks (Target)
Norwegian banks supporting PSD2 (via Open Banking provider):
- DNB
- Nordea
- Handelsbanken
- SpareBank 1
- Sbanken / DNB (merged)
5. Slack — Operational Alerting
5.1 Purpose
Slack receives operational alerts from Drop's internal alerting system (src/lib/alerts.ts).
Channel: #drop-ops on alai-talk.slack.com
5.2 Alert Types
| Alert | Severity | Trigger |
|---|---|---|
| App startup | Info (ℹ️) | Application boots successfully |
| App shutdown | Info (ℹ️) | SIGTERM/SIGINT received |
| Error spike | Critical (🚨) | > 5 errors in 60 seconds |
| Unhandled exception | Critical (🚨) | process.on('uncaughtException') |
| Custom alert | Variable | sendAlert() called manually |
5.3 Integration Details
| Parameter | Value |
|---|---|
| Method | HTTP POST to Slack Incoming Webhook URL |
| Auth | Webhook URL contains auth token |
| Cooldown | 10 minutes per alert title (in-memory) |
| Fallback | When SLACK_WEBHOOK_URL not set → console.log only |
5.4 Environment Variables
| Variable | Required | Description |
|---|---|---|
SLACK_WEBHOOK_URL |
Yes (prod) | Slack incoming webhook URL — stored in Secrets Manager |
5.5 Alert Format
🚨 Drop Production Alert — Critical
Title: Error spike detected
Message: 8 errors in the last 60 seconds
Time: 2026-02-23 14:30 UTC
6. BetterStack — External Uptime Monitoring
6.1 Purpose
BetterStack provides external uptime monitoring independent of Drop's infrastructure — detects total infrastructure failures that internal health checks miss.
6.2 Monitors Configured
| Monitor | URL | Check |
|---|---|---|
| Health Endpoint | https://drop.alai.no/api/health |
HTTP 200 + "status":"ok" |
| Landing Page | https://drop.alai.no |
HTTP 200 + Send penger |
| US East Health | https://drop.alai.no/api/health |
HTTP 200 from US East |
6.3 Alert Escalation
Minute 0: DOWN → Slack #drop-ops
Minute 5: Still down → Email [email protected]
Minute 15: Still down → SMS +47 40 47 42 51 (requires paid plan)
6.4 Status Page
Public status page: https://drop-status.betteruptime.com
Setup guide: docs/infrastructure/BETTERSTACK-SETUP.md
7. Deprecated Services
Swan Open Banking (Deprecated)
Status: DEPRECATED — no longer the planned Open Banking provider. Mock code exists at services/mock-swan.ts but will be removed.
Why deprecated: Commercial and technical reasons — specific provider TBD via procurement process.
Stripe Issuing (Mock/Dev — Future)
Status: MOCK ONLY — no Stripe SDK installed. File: services/mock-stripe.ts.
Purpose: Future card issuance feature (virtual + physical cards). All card feature flags default to false. Requires card issuing partner before activation.
Not to be confused with: Stripe Payments — Drop does NOT use Stripe for payments.
8. Service Initialization
Source: src/lib/services/index.ts
// Called once on app startup
await initializeServices();
// In tests — reset all mock state
resetMockServices();
Mock state uses in-memory storage (server-side) — resets on process restart.
Related Documents
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | Platform Architect (AI) | 2026-02-23 | |
| Reviewer | |||
| Approver | Alem Bašić |