@siglo/notifications Adapter Guide
[!IMPORTANT] Production Security & Hardening: For production deployment requirements, read Notification Hardening & Recovery. CallbackGETrequests are read-only confirmation previews; onlyPOSTrequests accept approval decisions. Configure anauthorize(payload, request)callback or explicitly opt intoallowBearerApproval: true.
@siglo/notifications provides multi-channel messaging, tamper-proof cryptographic approval tokens, and distributed durable callback resumption for Siglo workflows. It enables human-in-the-loop workflows (deployment gates, expense approvals, manual fraud reviews) with signed tokens, application-provided authorization, distributed concurrency control, and zero external SDK dependencies.
1. Overview & Problem Space
In enterprise backend workflows, human intervention and multi-channel notifications (Slack, Discord, Email, SMS, Push) are standard requirements. Implementing these with ad-hoc scripts introduces critical failure modes that@siglo/notifications helps address:
2. Architecture & Step Lifecycle Integration
Siglo executes steps through a formal 5-phase lifecycle:BEFORE → VALIDATION → ACTION → AFTER → COMPENSATE. @siglo/notifications hooks into every phase:
3. Installation & Requirements
Install@siglo/notifications alongside core Siglo and crypto packages:
@siglo/core:^1.0.0@siglo/crypto:^1.0.0@siglo/http:^1.0.0@siglo/scheduler(optional, for background timeout polling):^1.0.0@siglo/postgres(optional, for persistent checkpoints and shared revocation):^1.0.0
4. Multi-Channel Transports
@siglo/notifications provides zero-dependency transport implementations using native HTTPS with validated socket DNS resolution with strict network host assertions:
Transport Capability Matrix
Transport Registration
Transports can be registered globally in the application registry or passed directly to individual steps:5. Workflow Step Integration
sendNotificationStep(name, options)
Used for one-way alerts and messages with automatic idempotency deduplication and context redaction:
sendInteractiveApprovalStep(name, options)
Generates signed callback URLs for human sign-off, dispatches the interactive card/email, and registers saga compensation:
6. Cryptographic Action Tokens & Secret Rotation
Action tokens embed all callback state directly in a signed, URL-safe bearer token:Embedded Payload Structure
Security Defenses:
- Zero Database Lookups: The token is verified cryptographically before touching persistent storage.
- Timing Attack Defense: Comparisons use
timingSafeComparefrom@siglo/crypto. - Future Clock Skew Defense: Validates
issuedAt <= now + toleranceSeconds. Nonces are not a consumption registry; completed durable claims reject replay. - Key Rotation: Accepts both raw string secrets and
KeyRinginstances (StaticKeyRingfrom@siglo/crypto):
7. Web Framework Resumption Handlers
@siglo/notifications exports turnkey callback handlers for Express, Fastify, and Hono.
Security Headers & Sanitization
Every callback response automatically includes:Cache-Control: no-storeReferrer-Policy: no-referrerX-Content-Type-Options: nosniffContent-Security-Policy: default-src 'none'; frame-ancestors 'none'- Optional allowlisted CORS origin headers (
allowedOrigins: ['https://orchestrator.acme.com', 'https://dashboard.acme.com'])
Express Example
Fastify Example
Hono Example (Node)
HTTP Status Code Normalization
8. Distributed Resumption, Shared Revocation & Concurrency
Dual-Worker Atomic Claims
When an approval link is clicked, multiple workers behind a load balancer may receive duplicate requests simultaneously.@siglo/notifications integrates with @siglo/core’s atomic claimWaitRecord call:
- Worker 1 acquires the stack session lock, persists the accepted event, and atomically updates (
WAITING→RESUMING). Claim succeeds; workflow resumes. - Worker 2 cannot acquire the active session lock or finds the request already completed. Core throws a conflict exception, which the callback handler maps to HTTP
409 Conflict.
Shared PostgreSQL Revocation Store
When a workflow aborts or compensation runs, tokens must be revoked across all worker processes.9. Two-Way Twilio SMS Approvals
Approvers can reply directly via SMS withAPPROVE <ref> or REJECT <ref>. @siglo/notifications provides parseTwilioSmsReply to verify Twilio’s signed form webhooks:
10. Saga Compensation & Interactive Message Invalidation
If downstream steps fail or the workflow is cancelled, Siglo’s named compensation mechanism triggerscancelNotification:
- Slack Channels: The registered
SlackTransportinvokeschat.updateon the original message ID, removing interactive buttons and replacing the text with"Workflow cancelled". - Email & Discord: Calls
revocationStore.revoke(correlationKey). If an approver clicks an old email link later, the callback endpoint rejects it with HTTP409 Conflict.
11. Network Egress Security & ASL Perimeter
@siglo/notifications asserts all transport hostnames against Siglo’s AsyncLocalStorage (ASL) network policy:
- Static host assertions (
slack.com,api.resend.com,api.sendgrid.com,api.postmarkapp.com,discord.com,api.twilio.com) run in theBEFOREphase before any network connection is initiated. - HTTP requests reject redirects and validate DNS results in the actual socket lookup; injected clients must provide their own controls.
- Injected transports declare
allowedHostsdirectly on their class definition.
12. Observability, Telemetry & PII Redaction
Automatic Core Redaction
Siglo core automatically masks sensitive fields before emitting monitoring events:- Standard keys masked:
recipient,email,phone,slackUserId,slackChannelId,discordWebhookUrl,approvalTokens,approveUrl,rejectUrl,signingSecret. - Cyclic data structures are safely replaced with
'[Circular]'.
Custom Wildcard PII Masking (NotificationMonitor)
To mask domain-specific PII in execution logs and DevTools:
13. Testing & Deterministic Mocking
MockNotificationTransport
Exported from @siglo/notifications/testing to inspect payloads and URLs without real network traffic:
Fast-Forwarding Timeouts with FakeClock
To test approval timeouts deterministically:
[!NOTE]FakeClockadvances its own internal virtual timestamp counter (clock.now()). Callingclock.tick()flushes microtasks viasetImmediatewithout replacing global Node.jsDate.noworsetTimeout.
