diff --git a/API_SEQUENCE_DIAGRAMS.md b/API_SEQUENCE_DIAGRAMS.md
new file mode 100644
index 0000000..bf4f1b2
--- /dev/null
+++ b/API_SEQUENCE_DIAGRAMS.md
@@ -0,0 +1,528 @@
+# NotifyChain — API & Notification Sequence Diagrams
+
+> **Issue #354** — Visual reference for contributors who want to understand
+> exactly how notifications flow through the system before reading the code.
+>
+> **Related docs:**
+> - [Architecture Overview](ARCHITECTURE_OVERVIEW.md) — layer-by-layer system guide
+> - [Notification Failure Recovery](NOTIFICATION_FAILURE_RECOVERY.md) — retry semantics
+> - [Scheduled Notifications Delivery](SCHEDULED-NOTIFICATIONS-DELIVERY.md) — delivery guarantees
+> - [Listener API Reference](listener/API.md) — endpoint specification
+> - [Reorg Deduplication Monitoring](REORG-DEDUPLICATION-MONITORING.md) — duplicate-detection ops
+
+---
+
+## Table of Contents
+
+1. [Diagram 1 — Notification Request Flow](#diagram-1--notification-request-flow)
+2. [Diagram 2 — Event Processing Lifecycle](#diagram-2--event-processing-lifecycle)
+3. [Diagram 3 — Scheduled Notification Delivery](#diagram-3--scheduled-notification-delivery)
+4. [Diagram 4 — Retry & Failure Recovery](#diagram-4--retry--failure-recovery)
+5. [Diagram 5 — Dashboard Data Fetch](#diagram-5--dashboard-data-fetch)
+6. [Actor Reference](#actor-reference)
+
+---
+
+## Diagram 1 — Notification Request Flow
+
+**What this shows:** How an external client (a frontend app, CI job, or
+script) submits a notification payload to the Listener Service, how the
+backend validates it, and how it eventually reaches the subscriber.
+
+The flow divides into two sub-paths depending on whether the notification
+is *immediate* (sent right now) or *scheduled* (delivered at a future
+`executeAt` timestamp).
+
+```mermaid
+sequenceDiagram
+ autonumber
+
+ participant Client as Client
(Frontend / API caller)
+ participant API as Listener API
(events-server.ts)
+ participant Validator as Payload Validator
(notification-api.ts)
+ participant DB as SQLite Database
(scheduled_notifications)
+ participant Scheduler as Background Scheduler
(notification-scheduler.ts)
+ participant Discord as Discord Webhook
/ HTTP Target
+ participant Subscriber as Subscriber
(End-user / Webhook)
+
+ Note over Client,Subscriber: ── Immediate Notification (webhook trigger) ──
+
+ Client->>API: POST /api/webhooks
{ event, data }
+ X-Webhook-Signature header
+
+ API->>API: Extract key-id & signature headers
+ API->>API: Look up secret for key-id
+ API->>API: HMAC-SHA256 verify signature
+
+ alt Signature invalid or missing
+ API-->>Client: 401 Unauthorized
{ "error": "Invalid signature" }
+ else Signature verified
+ API-->>Client: 202 Accepted
{ "status": "accepted" }
+ Note over API: Webhook payload is now
accepted for processing
+ end
+
+ Note over Client,Subscriber: ── Scheduled Notification ──
+
+ Client->>API: POST /api/schedule
{ executeAt, payload, targetRecipient,
notificationType, maxRetries, priority }
+
+ API->>Validator: scheduleNotification(input)
+ Validator->>Validator: Validate executeAt is a future Date
+ Validator->>Validator: Validate payload is an object
+ Validator->>Validator: Validate targetRecipient is present
+ Validator->>Validator: Check payload size ≤ MAX_PAYLOAD_SIZE_BYTES (64 KB)
+
+ alt Validation failure
+ Validator-->>API: throw Error (invalid input)
+ API-->>Client: 400 Bad Request
{ "error": "..." }
+ else Payload too large
+ Validator-->>API: throw PayloadTooLargeError
+ API-->>Client: 413 Payload Too Large
{ "error": "Notification payload is too large: ..." }
+ else Scheduler disabled
+ API-->>Client: 503 Service Unavailable
{ "error": "Scheduler not enabled" }
+ else Validation passed
+ Validator->>DB: INSERT scheduled_notifications
(status=PENDING, executeAt, payload, …)
+ DB-->>Validator: notification id
+ Validator-->>API: notification id
+ API-->>Client: 201 Created
{ "id": 42 }
+ end
+
+ Note over Scheduler,Subscriber: ── Background scheduler tick (every 10 s) ──
+
+ loop Every pollIntervalMs (default 10 s)
+ Scheduler->>DB: SELECT WHERE status=PENDING
AND execute_at ≤ NOW()
AND (next_retry_at IS NULL OR ≤ NOW())
+ DB-->>Scheduler: batch of due notifications
+
+ loop For each due notification
+ Scheduler->>DB: UPDATE status=PROCESSING
WHERE id=? AND status=PENDING
(atomic distributed lock)
+ alt Row already locked by another worker
+ DB-->>Scheduler: changedRows = 0 → skip
+ else Lock acquired
+ Scheduler->>Discord: POST webhook payload
to targetRecipient URL
+ Discord-->>Scheduler: 200 OK / error
+
+ alt Delivery success
+ Scheduler->>DB: UPDATE status=COMPLETED
+ Discord-->>Subscriber: Notification delivered
+ else Delivery failed, retries remain
+ Scheduler->>DB: UPDATE status=PENDING
retry_count++, next_retry_at = NOW() + backoff
+ else Max retries exhausted
+ Scheduler->>DB: UPDATE status=FAILED
last_error = error message
+ end
+ end
+ end
+ end
+```
+
+### Key points
+
+- **Webhook verification** uses HMAC-SHA256 with pre-shared secrets
+ configured via `WEBHOOK_SECRETS`. If no secrets are configured every
+ webhook call returns `401`.
+- **Payload size** is validated before any database write. The default
+ ceiling is **64 KB** (configurable via `MAX_PAYLOAD_SIZE_BYTES`).
+- **Distributed lock** prevents two scheduler workers from processing the
+ same notification simultaneously. The `WHERE status = 'PENDING'`
+ predicate is the atomicity boundary.
+- **Exponential backoff** doubles the retry delay at each attempt.
+ `RETRY_BASE_DELAY_MS` (default 5 s) × `RETRY_MULTIPLIER` (default 2)
+ up to `RETRY_MAX_DELAY_MS` (default 1 h).
+
+---
+
+## Diagram 2 — Event Processing Lifecycle
+
+**What this shows:** The on-chain → off-chain path: how a contract state
+change on the Stellar network becomes a notification delivered to a
+subscriber and an entry visible in the dashboard.
+
+```mermaid
+sequenceDiagram
+ autonumber
+
+ participant User as User / dApp
+ participant Contract as Soroban Smart Contract
(TaskBounty / AutoShare)
+ participant Stellar as Stellar Network
(RPC Node)
+ participant Subscriber as EventSubscriber
(event-subscriber.ts)
+ participant PersistDedup as Persistent Deduplicator
(event-deduplication-service.ts)
+ participant MemDedup as In-Memory Deduplicator
(notification-deduplicator.ts)
+ participant Registry as Event Registry
(event-registry.ts)
+ participant Discord as DiscordNotificationService
(discord-notification.ts)
+ participant RetryQ as Retry Queue
(notification-retry-queue.ts)
+ participant Dashboard as Dashboard
(React + Vite)
+ participant Subscriber2 as Subscriber
(Discord / Webhook)
+
+ Note over User,Stellar: ── On-chain action ──
+
+ User->>Contract: invoke contract function
(e.g. create_task(), create())
+ Contract->>Contract: Validate input & auth
+ Contract->>Contract: Mutate persistent storage
+ Contract->>Stellar: emit typed Soroban event
topics: [eventName, category, priority]
data: { id, creator, … }
+ Stellar-->>User: transaction result
+
+ Note over Subscriber,Registry: ── Off-chain polling loop (every 30 s default) ──
+
+ loop Every pollIntervalMs
+ Subscriber->>Stellar: getEvents({ contractIds, cursor, limit:100 })
+ Stellar-->>Subscriber: EventResponse[]
{ id, ledger, topic, value, txHash }
+
+ Subscriber->>Subscriber: Detect potential reorg
(compare event ledger with last cursor)
+
+ alt Reorg detected (new ledger < last ledger)
+ Subscriber->>PersistDedup: detectReorg(contractAddress, ledger)
+ PersistDedup->>PersistDedup: Compare with polling_cursors table
+ PersistDedup-->>Subscriber: reorgDetected = true → log warning
+ end
+
+ loop For each received event
+ Subscriber->>Subscriber: validateEventPayload(event)
matchesEventFilter(eventName, config.events)
+
+ alt Event filtered out or invalid
+ Subscriber->>Subscriber: skip (log warning)
+ else Event passes filter
+
+ Note over Subscriber,PersistDedup: ── Layer 1: Persistent deduplication ──
+
+ Subscriber->>PersistDedup: isDuplicate(eventId, contractAddress)
+ PersistDedup->>PersistDedup: SELECT FROM processed_events
WHERE fingerprint = contract:eventId
+
+ alt Already in processed_events
+ PersistDedup-->>Subscriber: { isDuplicate: true, isReorgDuplicate: bool }
+ Subscriber->>PersistDedup: recordProcessedEvent(…, status=SKIPPED)
+ Subscriber->>Subscriber: skip — notification already sent
+ else New event
+ PersistDedup-->>Subscriber: { isDuplicate: false }
+
+ Note over Subscriber,MemDedup: ── Layer 2: In-memory deduplication ──
+
+ Subscriber->>MemDedup: isDuplicate(fingerprint)
(60-second LRU window)
+
+ alt Seen within last 60 s
+ MemDedup-->>Subscriber: duplicate → skip
+ else Not in cache
+ MemDedup->>MemDedup: Mark fingerprint as seen
+
+ Subscriber->>Registry: addFromInput(event)
→ normalize & store DisplayEvent
+ Registry-->>Subscriber: DisplayEvent
+
+ alt Discord configured & category enabled
+ Subscriber->>Discord: sendEventNotification(event, contractConfig)
+ Discord->>Discord: generateFingerprint(eventId, address)
+ Discord->>Discord: Build embed message
+ Discord->>Subscriber2: POST https://discord.com/api/webhooks/…
+ Subscriber2-->>Discord: 200 OK / HTTP error
+
+ alt Discord delivery success
+ Discord-->>Subscriber: true (notificationSent)
+ Subscriber->>PersistDedup: recordProcessedEvent(…, notificationSent=true, PROCESSED)
+ else Discord delivery failed
+ Discord-->>Subscriber: false
+ Subscriber->>RetryQ: enqueue(event, contractConfig)
+ Subscriber->>PersistDedup: recordProcessedEvent(…, notificationSent=false, ERROR)
+ end
+ else Discord not configured
+ Subscriber->>PersistDedup: recordProcessedEvent(…, notificationSent=false, PROCESSED)
+ end
+ end
+ end
+ end
+ end
+
+ Subscriber->>Stellar: persist cursor (last ledger seen)
+ Subscriber->>PersistDedup: updatePollingCursor(address, cursor, ledger)
+ end
+
+ Note over Dashboard,Subscriber2: ── Dashboard rendering ──
+
+ Dashboard->>+Registry: GET /api/events[?limit=N]
+ Registry-->>-Dashboard: { count, events: DisplayEvent[] }
+ Dashboard->>Dashboard: Render event list & cards
+```
+
+### Key points
+
+- **Two deduplication layers** work in series. The persistent layer
+ (`EventDeduplicationService` + SQLite `processed_events` table) catches
+ duplicates across restarts and blockchain reorgs. The in-memory layer
+ (`NotificationDeduplicator` LRU cache) catches rapid duplicates cheaply
+ without a database round-trip.
+- **Reorg detection** compares the incoming event's ledger number against
+ the last-known cursor stored in `polling_cursors`. A decreasing ledger
+ signals a reorganization.
+- **Event Registry** is the in-memory store exposed by `GET /api/events`.
+ The dashboard polls it; it is NOT the source of truth — SQLite is.
+- **Analytics** are recorded by `NotificationAnalyticsAggregator` for
+ every outcome (success, failure, skipped, retry), viewable at
+ `GET /api/analytics`.
+
+---
+
+## Diagram 3 — Scheduled Notification Delivery
+
+**What this shows:** The lifecycle of a notification from its creation via
+`POST /api/schedule` through to delivery and terminal state, including the
+distributed-lock handshake that makes multi-instance deployments safe.
+
+```mermaid
+sequenceDiagram
+ autonumber
+
+ participant Caller as API Caller
+ participant API as Listener API
+ participant NotifAPI as NotificationAPI
(notification-api.ts)
+ participant DB as SQLite
(scheduled_notifications)
+ participant Scheduler as NotificationScheduler
(background tick)
+ participant Discord as Discord
/ HTTP Webhook
+
+ Caller->>API: POST /api/schedule
{ executeAt: "2026-07-24T10:00:00Z",
payload: { message: "…" },
targetRecipient: "https://discord.com/…",
notificationType: "discord",
maxRetries: 3, priority: 5 }
+
+ API->>NotifAPI: scheduleNotification(input)
+ NotifAPI->>NotifAPI: validate executeAt (future date)
+ NotifAPI->>NotifAPI: validate payload (object, ≤64 KB)
+ NotifAPI->>NotifAPI: validate targetRecipient (non-empty)
+ NotifAPI->>DB: INSERT scheduled_notifications
status=PENDING, retry_count=0
+
+ DB-->>NotifAPI: id = 42
+ NotifAPI-->>API: 42
+ API-->>Caller: 201 { "id": 42 }
+
+ Note over DB: Row sits PENDING
until executeAt ≤ NOW()
+
+ loop Scheduler tick (every 10 s)
+ Scheduler->>DB: SELECT id WHERE status=PENDING
AND execute_at ≤ NOW()
AND (next_retry_at IS NULL OR ≤ NOW())
LIMIT batchSize
+
+ alt No due notifications
+ DB-->>Scheduler: []
+ else Due notifications found
+ DB-->>Scheduler: [{ id:42, payload, targetRecipient, … }]
+
+ Scheduler->>DB: UPDATE status=PROCESSING
processor_id=worker-1
lock_expires_at=NOW()+60s
WHERE id=42 AND status=PENDING
+
+ alt Lock already held (another worker)
+ DB-->>Scheduler: changedRows=0 → skip row
+ else Lock acquired
+ DB-->>Scheduler: changedRows=1
+
+ Scheduler->>Discord: POST targetRecipient
JSON payload
+
+ alt HTTP 2xx — success
+ Discord-->>Scheduler: 200 OK
+ Scheduler->>DB: UPDATE status=COMPLETED
processing_completed_at=NOW()
+ else HTTP error — retries remain
+ Discord-->>Scheduler: 4xx / 5xx / timeout
+ Scheduler->>DB: UPDATE status=PENDING
retry_count++
next_retry_at=NOW() + backoff
last_error="HTTP 500"
+ Note over DB: Row re-enters PENDING pool
with a future next_retry_at
+ else Max retries exhausted
+ Discord-->>Scheduler: error
+ Scheduler->>DB: UPDATE status=FAILED
last_error="Max retries exceeded"
+ end
+ end
+ end
+ end
+
+ Note over Caller,DB: ── Query status at any time ──
+
+ Caller->>API: GET /api/schedule/42
+ API->>DB: SELECT * WHERE id=42
+ DB-->>API: { id:42, status:"COMPLETED", … }
+ API-->>Caller: 200 { notification object }
+```
+
+### Notification states
+
+| State | Meaning |
+|-------|---------|
+| `PENDING` | Waiting for `executeAt` to arrive (or `next_retry_at` to clear). |
+| `PROCESSING` | Locked by a worker; delivery in progress. Lock expires after 60 s. |
+| `COMPLETED` | Delivered successfully. |
+| `FAILED` | Max retries exhausted or unrecoverable error. |
+| `CANCELLED` | Explicitly cancelled via API before delivery. |
+
+---
+
+## Diagram 4 — Retry & Failure Recovery
+
+**What this shows:** What happens when a Discord notification fails — both
+the in-process `NotificationRetryQueue` path (for real-time event
+notifications) and the persistent scheduler retry path (for scheduled
+notifications).
+
+```mermaid
+sequenceDiagram
+ autonumber
+
+ participant Subscriber as EventSubscriber
+ participant Discord as DiscordNotificationService
+ participant RetryQ as NotificationRetryQueue
(in-memory, 5 retries)
+ participant Scheduler as NotificationScheduler
(DB-backed, configurable)
+ participant DB as SQLite
+
+ Note over Subscriber,RetryQ: ── Path A: Real-time event notification retry ──
+
+ Subscriber->>Discord: sendEventNotification(event, contractConfig)
+ Discord->>Discord: POST to webhook URL
+ Discord-->>Subscriber: false (delivery failed)
+
+ Subscriber->>RetryQ: enqueue(event, contractConfig, requestId)
+ RetryQ->>RetryQ: Store { event, retryCount=0,
nextRetryAt=NOW()+baseDelay }
+
+ loop RetryQueue tick (every 5 s)
+ RetryQ->>RetryQ: Filter items where nextRetryAt ≤ NOW()
+
+ alt Item due for retry
+ RetryQ->>Discord: sendEventNotification(event, contractConfig)
+
+ alt Success
+ Discord-->>RetryQ: true
+ RetryQ->>RetryQ: Remove from queue
+ Note over RetryQ: Analytics: outcome=success
+ else Failed, retries < maxRetries (default 5)
+ Discord-->>RetryQ: false
+ RetryQ->>RetryQ: retryCount++
nextRetryAt = NOW() + baseDelay × 2^retryCount
+ Note over RetryQ: Exponential backoff
5s → 10s → 20s → 40s → 80s
+ else Failed, maxRetries exhausted
+ Discord-->>RetryQ: false
+ RetryQ->>RetryQ: Remove from queue (permanently dropped)
+ Note over RetryQ: Analytics: outcome=failure
Log: "Max retries exceeded"
+ end
+ end
+ end
+
+ Note over Scheduler,DB: ── Path B: Scheduled notification retry (DB-backed) ──
+
+ Scheduler->>DB: SELECT PENDING due rows
+ Scheduler->>DB: Lock row (status=PROCESSING)
+ Scheduler->>Discord: POST webhook
+ Discord-->>Scheduler: HTTP error
+
+ alt retry_count < max_retries
+ Scheduler->>DB: status=PENDING
retry_count = retry_count + 1
next_retry_at = NOW() + baseDelayMs × multiplier^retry_count
last_error = errorMessage
+ Note over DB: Survives service restart
(persisted in SQLite)
+ else retry_count ≥ max_retries
+ Scheduler->>DB: status=FAILED
last_error = "Max retries exceeded"
+ end
+
+ Note over Scheduler,DB: ── Stale lock recovery ──
+
+ Scheduler->>DB: SELECT WHERE status=PROCESSING
AND lock_expires_at < NOW()
+ DB-->>Scheduler: Stale locked row (worker crashed mid-delivery)
+ Scheduler->>DB: UPDATE status=PENDING
processor_id=NULL, lock_expires_at=NULL
retry_count++
+ Note over DB: Row re-enters retry pool
(at-least-once delivery guarantee)
+```
+
+### Key points
+
+- **Two independent retry systems** exist. `NotificationRetryQueue` handles
+ real-time event notifications in memory (no persistence across restarts).
+ `NotificationScheduler` handles scheduled notifications with full
+ SQLite persistence and distributed locking.
+- **Stale lock recovery** runs each scheduler tick. A lock is stale if
+ `lock_expires_at < NOW()` and status is still `PROCESSING`. This
+ catches crashes mid-delivery and re-queues the row.
+- **At-least-once guarantee**: the DB write completes before the delivery
+ attempt is considered definitive. If the process crashes after delivery
+ but before marking COMPLETED, the row will be retried — the subscriber
+ must be idempotent.
+
+---
+
+## Diagram 5 — Dashboard Data Fetch
+
+**What this shows:** How the React dashboard reads data from the Listener
+API, including the events feed, analytics, and scheduler stats.
+
+```mermaid
+sequenceDiagram
+ autonumber
+
+ participant User as Human Operator
+ participant Dashboard as Dashboard
(React + Vite)
+ participant EventsStore as Zustand Event Store
(eventStore.ts)
+ participant API as Listener API
(:8787)
+ participant Registry as Event Registry
(in-memory)
+ participant DB as SQLite
+
+ User->>Dashboard: Open dashboard in browser
+
+ Note over Dashboard,Registry: ── Initial load ──
+
+ Dashboard->>EventsStore: fetchEvents()
+ EventsStore->>API: GET /api/events?limit=100
+ API->>Registry: getEvents(100)
+ Registry-->>API: DisplayEvent[]
+ API-->>EventsStore: 200 { count, events }
+ EventsStore->>EventsStore: Update Zustand store
+ EventsStore-->>Dashboard: Re-render event list
+
+ Note over Dashboard,DB: ── Health check ──
+
+ Dashboard->>API: GET /health
+ API->>API: checkStellarRpc(rpcUrl)
checkDiscord(webhookUrl)
+ API-->>Dashboard: 200 { status: "ok"|"degraded"|"error",
services: { stellarRpc, discord, eventRegistry } }
+
+ Note over Dashboard,DB: ── Analytics snapshot ──
+
+ Dashboard->>API: GET /api/analytics
+ API-->>Dashboard: 200 { totalRecorded, byType, byContract,
hourlyBuckets, errorBreakdown }
+
+ Note over Dashboard,DB: ── Scheduler stats ──
+
+ Dashboard->>API: GET /api/schedule/stats
+ API->>DB: SELECT COUNT(*) GROUP BY status
+ DB-->>API: { pending, processing, completed, failed, cancelled }
+ API-->>Dashboard: 200 { pending: 3, completed: 142, … }
+
+ Note over Dashboard,Registry: ── Periodic refresh (poll) ──
+
+ loop Every N seconds (client-side interval)
+ Dashboard->>API: GET /api/events?limit=100
+ API->>Registry: getEvents(100)
+ Registry-->>API: updated DisplayEvent[]
+ API-->>Dashboard: 200 { count, events }
+ Dashboard->>Dashboard: Diff and update store → re-render new events
+ end
+
+ Note over User,Dashboard: ── Search & filter ──
+
+ User->>Dashboard: Type in search bar / apply filters
+ Dashboard->>EventsStore: applyFilters(query, contractAddress, eventType)
+ EventsStore->>EventsStore: Filter in-memory store
(client-side, no extra API call)
+ EventsStore-->>Dashboard: Filtered DisplayEvent[]
+ Dashboard->>Dashboard: Re-render filtered list
+```
+
+### Key points
+
+- The dashboard is **strictly read-only**. It never writes to the Listener
+ API or to any contract.
+- All **search and filtering** happens client-side over the already-fetched
+ event array; no extra API calls are needed per keystroke.
+- The **health endpoint** (`GET /health`) aggregates Stellar RPC reachability
+ and Discord webhook status. It returns `503` if the Stellar RPC is
+ unreachable.
+- **Rate limiting** is enforced by the Listener API (default 60 req/min per
+ client). Dashboard polling intervals should respect this budget.
+
+---
+
+## Actor Reference
+
+| Actor | Code location | Description |
+|-------|---------------|-------------|
+| **Client** | Any HTTP caller | Frontend, CI job, or script calling the Listener REST API |
+| **Listener API** | `listener/src/api/events-server.ts` | Node.js HTTP server handling all REST endpoints |
+| **Payload Validator** | `listener/src/services/notification-api.ts`, `listener/src/utils/payload-size-validator.ts` | Validates `executeAt`, `payload`, `targetRecipient`, and payload byte size |
+| **SQLite Database** | `listener/src/database/` | Persists scheduled notifications, processed events, templates, rate-limit events |
+| **EventSubscriber** | `listener/src/services/event-subscriber.ts` | Polls Stellar RPC for contract events on a configurable interval |
+| **Persistent Deduplicator** | `listener/src/services/event-deduplication-service.ts` | Checks `processed_events` table; detects blockchain reorgs |
+| **In-Memory Deduplicator** | `listener/src/services/notification-deduplicator.ts` | LRU cache (60 s window) for rapid duplicate suppression |
+| **Event Registry** | `listener/src/store/event-registry.ts` | In-memory ring buffer of recent `DisplayEvent` objects; source for `GET /api/events` |
+| **DiscordNotificationService** | `listener/src/services/discord-notification.ts` | Formats events as Discord embeds and POSTs to the webhook URL |
+| **NotificationRetryQueue** | `listener/src/services/notification-retry-queue.ts` | In-memory retry queue for failed real-time event notifications |
+| **NotificationScheduler** | `listener/src/services/notification-scheduler.ts` | Background tick that processes due `scheduled_notifications` rows |
+| **Soroban Contract** | `contract/contracts/hello-world/`, `Documents/Task Bounty/` | On-chain Rust/Soroban contracts that emit typed events |
+| **Stellar Network** | External | Stellar RPC node returning event streams via `getEvents()` |
+| **Dashboard** | `dashboard/src/` | React + Vite SPA; read-only consumer of the Listener API |
+| **Subscriber** | External | Discord channel, webhook target, or any HTTP endpoint receiving notifications |
+
+---
+
+*Diagrams reflect the codebase as of 2026-07-23. Maintained as part of issue #354.*
diff --git a/ARCHITECTURE_OVERVIEW.md b/ARCHITECTURE_OVERVIEW.md
index 62f27ec..424bdf5 100644
--- a/ARCHITECTURE_OVERVIEW.md
+++ b/ARCHITECTURE_OVERVIEW.md
@@ -473,6 +473,23 @@ maintainers track open questions actively.
## 9. Related Resources
+### Sequence Diagrams (Issue #354)
+
+Visual walkthroughs of the key data flows — the fastest way to build a
+mental model of how the system moves data end-to-end:
+
+| Diagram | What it covers |
+|---------|----------------|
+| [Notification Request Flow](API_SEQUENCE_DIAGRAMS.md#diagram-1--notification-request-flow) | Client → API validation → scheduled delivery to subscriber |
+| [Event Processing Lifecycle](API_SEQUENCE_DIAGRAMS.md#diagram-2--event-processing-lifecycle) | On-chain contract event → EventSubscriber → deduplication → Discord / dashboard |
+| [Scheduled Notification Delivery](API_SEQUENCE_DIAGRAMS.md#diagram-3--scheduled-notification-delivery) | POST /api/schedule → SQLite → background scheduler → delivery states |
+| [Retry & Failure Recovery](API_SEQUENCE_DIAGRAMS.md#diagram-4--retry--failure-recovery) | In-memory retry queue vs DB-backed retry; stale lock recovery |
+| [Dashboard Data Fetch](API_SEQUENCE_DIAGRAMS.md#diagram-5--dashboard-data-fetch) | React dashboard polling /api/events, /health, /api/analytics |
+
+→ **[View all sequence diagrams →](API_SEQUENCE_DIAGRAMS.md)**
+
+---
+
### Subsystem Architecture Docs (read alongside this guide)
- `Documents/Task Bounty/ARCHITECTURE.md` — TaskBounty contract
diff --git a/README.md b/README.md
index 2a9468a..26628e0 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,11 @@ run and developed independently.
>
> A more detailed, contract-level architecture write-up lives in
> [`Documents/Task Bounty/ARCHITECTURE.md`](Documents/Task%20Bounty/ARCHITECTURE.md).
+>
+> **New to the codebase?** The
+> [API & Notification Sequence Diagrams](API_SEQUENCE_DIAGRAMS.md)
+> visually trace every major data flow — from client request to subscriber
+> delivery and from on-chain event to dashboard render.
### Contract Responsibilities
@@ -188,6 +193,8 @@ Notify-Chain/
## Event Flow
+> 📊 **See also:** [API & Notification Sequence Diagrams](API_SEQUENCE_DIAGRAMS.md) — Mermaid diagrams that visually trace the complete notification request flow, on-chain event processing lifecycle, scheduled delivery states, retry/failure recovery, and dashboard data fetch.
+
### End-to-End Notification Flow
This is how a single on-chain action becomes a delivered notification: