Event Schema Documentation
Event Schema Documentation
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 — event-driven architecture not yet implemented |
1. Overview
Drop's current architecture is a synchronous monolith — there is no internal event bus or message queue (no BullMQ, SQS, or RabbitMQ). All operations are handled synchronously within Next.js API route handlers.
This document covers:
- External webhook events received from Sumsub (KYC status updates) — the only production external event source
- Internal audit log events — database-level event tracking for compliance
- Planned event schema — for future async processing when an event bus is added
2. External Webhook Events
2.1 Sumsub — KYC Status Webhook
Source: src/lib/services/mock-sumsub.ts, production Sumsub docs: https://docs.sumsub.com/reference/applicant-review
Sumsub sends webhook events when a KYC applicant's review status changes.
Endpoint: POST /api/kyc/webhook (TBD — pending Open Banking provider integration)
Webhook signature verification: HMAC-SHA256 using SUMSUB_SECRET_KEY. Reject any webhook with invalid signature.
Event: applicantReviewed
Sent when Sumsub completes review of a KYC applicant.
{
"type": "applicantReviewed",
"reviewStatus": "completed",
"applicantId": "sumsub_applicant_id",
"externalUserId": "usr_a1b2c3d4",
"reviewResult": {
"reviewAnswer": "GREEN",
"rejectLabels": [],
"reviewRejectType": null
},
"createdAt": "2026-02-23T12:00:00Z"
}
| Field | Type | Values | Description |
|---|---|---|---|
type |
string | applicantReviewed |
Event type |
reviewAnswer |
string | GREEN, RED, RETRY |
Verification outcome |
externalUserId |
string | Drop user ID | Links to Drop users.id |
rejectLabels |
array | e.g., ["DOCUMENT_UNREADABLE"] |
Rejection reasons (if RED/RETRY) |
Drop action on receipt:
switch (reviewAnswer) {
case 'GREEN':
// Update users SET kyc_status = 'approved' WHERE id = externalUserId
break;
case 'RED':
// Update users SET kyc_status = 'rejected' WHERE id = externalUserId
break;
case 'RETRY':
// Update users SET kyc_status = 'pending' WHERE id = externalUserId
// User must resubmit documents
break;
}
Event: applicantPending
Sent when applicant documents are submitted and awaiting review.
{
"type": "applicantPending",
"applicantId": "...",
"externalUserId": "usr_...",
"createdAt": "2026-02-23T12:00:00Z"
}
Drop action: Update users SET kyc_status = 'pending'.
2.2 Open Banking Provider — Payment Webhooks (Planned)
When an Open Banking provider is selected, the following webhook events will be received for PISP payment status updates.
Endpoint: POST /api/payments/webhook (future)
Event: payment.settled
{
"event": "payment.settled",
"paymentId": "provider_payment_id",
"dropTransactionId": "tx_rem_abc123",
"status": "completed",
"settledAt": "2026-02-23T14:30:00Z",
"amount": 2000,
"currency": "NOK"
}
Drop action: UPDATE transactions SET status = 'completed', completed_at = NOW() WHERE id = dropTransactionId
Event: payment.failed
{
"event": "payment.failed",
"paymentId": "provider_payment_id",
"dropTransactionId": "tx_rem_abc123",
"status": "failed",
"failureReason": "InsufficientFunds",
"failedAt": "2026-02-23T14:30:00Z"
}
Drop action: UPDATE transactions SET status = 'failed' WHERE id = dropTransactionId, refund bank account balance.
2.3 BankID — No Webhook Events
BankID OIDC is a synchronous request-response flow. No webhooks are received from BankID. The callback URL (GET /api/auth/bankid/callback) handles the code exchange synchronously.
3. Internal Audit Log Events
Drop maintains an audit_log table for all significant application events. This functions as an internal event log for compliance (AML, GDPR) and security investigations.
3.1 Audit Log Schema
CREATE TABLE audit_log (
id TEXT PRIMARY KEY,
action TEXT NOT NULL, -- Event type (see below)
user_id TEXT, -- Acting user (null for system events)
resource_type TEXT, -- Entity type (user, transaction, secret, etc.)
resource_id TEXT, -- Entity ID
details TEXT, -- JSON blob with event-specific data
ip_address TEXT, -- Client IP at time of action
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
3.2 Audit Event Types
| Action | Trigger | Resource Type | Details |
|---|---|---|---|
user_registered |
New user created via BankID | user |
{ kyc_method, auth_provider } |
kyc_approved |
KYC status → approved | user |
{ sumsub_applicant_id, review_answer } |
kyc_rejected |
KYC status → rejected | user |
{ reject_labels, review_answer } |
transaction_created |
Remittance or QR payment initiated | transaction |
{ type, amount, fee, currency } |
transaction_completed |
Payment confirmed by provider | transaction |
{ settled_at, provider_ref } |
transaction_failed |
Payment rejected | transaction |
{ failure_reason } |
session_created |
User logged in | session |
{ auth_method } |
sessions_revoked |
User logged out | session |
{ revoked_count } |
account_deleted |
GDPR erasure request | user |
{ deleted_at, retention_note } |
data_exported |
GDPR data export | user |
{ export_timestamp } |
consent_granted |
GDPR consent given | consent |
{ consent_type, ip_address } |
consent_withdrawn |
GDPR consent revoked | consent |
{ consent_type, ip_address } |
complaint_submitted |
User complaint filed | complaint |
{ category, subject } |
aml_alert_created |
AML monitoring flagged activity | aml_alert |
{ alert_type, severity, transaction_id } |
str_filed |
STR filed with Finanstilsynet | str_report |
{ filed_at, case_number } |
secret_rotated |
Secret key rotated | secret |
{ provider, key_name, rotated_at } |
merchant_registered |
New merchant account | merchant |
{ org_number, business_name } |
3.3 Example Audit Log Entry
{
"id": "aud_1a2b3c4d5e6f7890",
"action": "transaction_created",
"user_id": "usr_abc123",
"resource_type": "transaction",
"resource_id": "tx_rem_xyz789",
"details": {
"type": "remittance",
"amount": 2000,
"fee": 10,
"currency": "NOK",
"recipient_country": "RS",
"exchange_rate": 11.7
},
"ip_address": "85.20.12.45",
"timestamp": "2026-02-23T14:30:00.000Z"
}
3.4 Querying Audit Logs
-- Recent user activity
SELECT * FROM audit_log
WHERE user_id = 'usr_abc123'
ORDER BY timestamp DESC
LIMIT 50;
-- Security: all secret rotations
SELECT * FROM audit_log
WHERE action = 'secret_rotated'
ORDER BY timestamp DESC;
-- AML: suspicious transactions
SELECT al.* FROM audit_log al
JOIN transactions t ON al.resource_id = t.id
WHERE al.action = 'transaction_created'
AND t.send_amount > 50000 -- High-value transactions
AND al.timestamp > NOW() - INTERVAL '24 hours'
ORDER BY al.timestamp DESC;
Retention: Audit logs retained for 5 years per hvitvaskingsloven (Norwegian AML law).
4. Planned Event-Driven Architecture (Future)
When Drop scales beyond MVP, an event bus will decouple synchronous operations:
4.1 Proposed Event Bus
Technology options:
- AWS SQS + SNS (native AWS, fits existing infrastructure)
- BullMQ + Redis (simpler, Node.js native)
Recommendation: SQS for production reliability.
4.2 Planned Event Topics
| Topic | Publisher | Subscribers | Purpose |
|---|---|---|---|
drop.transaction.initiated |
Payment service | PISP provider, Audit, Notification | Trigger payment + notify user |
drop.transaction.settled |
Open Banking webhook handler | Audit, Notification, AML | Update transaction status |
drop.kyc.status_changed |
Sumsub webhook handler | Audit, User service | Update user KYC status |
drop.user.registered |
Auth service | KYC, Notification | Trigger KYC flow |
drop.aml.alert |
AML monitoring | Compliance, Notification | Flag suspicious activity |
4.3 Planned Event Envelope
{
"eventId": "evt_unique_id",
"eventType": "drop.transaction.initiated",
"version": "1.0",
"timestamp": "2026-02-23T14:30:00Z",
"source": "drop-payment-service",
"correlationId": "req_abc123",
"data": {
"transactionId": "tx_rem_xyz",
"userId": "usr_abc",
"amount": 2000,
"currency": "NOK"
}
}
5. Slack Alert Events (Operational)
Slack operational alerts from src/lib/alerts.ts function as a simple event notification system:
| Severity | Event | Trigger |
|---|---|---|
info |
App startup | Application boots |
info |
App shutdown | Graceful shutdown |
critical |
Error spike | > 5 errors in 60 seconds |
critical |
Unhandled exception | Process error handler |
These are fire-and-forget HTTP POST calls — no acknowledgement or retry logic.
Related Documents
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | Platform Architect (AI) | 2026-02-23 | |
| Reviewer | |||
| Approver | Alem Bašić |