SRS: UC_9.14 — Flow 32: Velocity Reward Engine
| Field | Value |
|---|---|
| UC_ID | UC_9.14 |
| Version | v1.1 |
| Last Updated | 2026-11-05 |
| BA PiC | linh.nguyen34 |
| Category | STAGE 9: GROWTH & REFERRAL |
| Module | Referral Loyalty Loop |
| Function | Flow 32 — Velocity Reward Engine |
| Document References | RFQ_ Stack Trading Prop Tech V7.pdf (§Velocity Reward Engine) · Zapier Integration V7.pdf (§Flow 32) |
1. UC Index
| UC_ID | Function | Description |
|---|---|---|
| UC_9.14 | Flow 32 — Velocity Reward Engine | Issues 100%-off EVAL_L1 promo codes at referral milestones (3 and 6 successful referrals). Triggered by Everflow S2S webhook (EV_MILESTONE_3_REFS / EV_MILESTONE_6_REFS). Executes 4-step pipeline: idempotency gate → sanctions check → promo code generation → Klaviyo email → Discord broadcast. CFTC/NFA-compliant audit trail. |
UC_9.14 — Flow 32: Velocity Reward Engine
| Field | Value |
|---|---|
| Use Case | Velocity Reward Engine |
| Description | At referral milestones of 3 or 6 successful referrals, Everflow fires an S2S webhook (EV_MILESTONE_3_REFS or EV_MILESTONE_6_REFS). Middleware executes: (1) idempotency gate via PostgreSQL SELECT ... FOR UPDATE + UNIQUE index on provider_event_id, (2) sanctions screening against Compliance_geo_restrictions, (3) promo code generation (EVAL_L1, 100% discount) inserted into promo_codes table, (4) Klaviyo Velocity_Reward_Unlocked email, (5) Discord broadcast to #trading-floor-announcements via Redis queue with 5 msg/5 s rate cap. Audit written to bps_audit_logs and replicated to S3 Glacier Object Lock (WORM) for CFTC/NFA 5-year retention. |
| Zapier Flow | Flow 32 (Velocity Reward Engine) |
| Trigger Source | Everflow S2S Webhook |
| 3rd Party | Everflow · Klaviyo · Discord Bot API |
| Middleware Endpoint | POST /api/referral/velocity-reward |
| Release | Release 2 (QnA_BACKGROUND_MECHANISMS_CRON.md [BG-012] — client sheet 2026-11-04) |
2. Trigger
Event: Everflow S2S webhook received by Middleware at POST /api/referral/velocity-reward.
| Event Name | Milestone | Meaning |
|---|---|---|
EV_MILESTONE_3_REFS | 3 successful referrals | Referrer reaches 3-referral velocity tier |
EV_MILESTONE_6_REFS | 6 successful referrals | Referrer reaches 6-referral velocity tier |
Everflow fires the webhook when the referral count for origin_user_id increments past the milestone threshold. Middleware is the sole entry point — the flow does not pass through Zapier.
(Source: Zapier Integration V7.pdf §Flow 32)
3. Pre-conditions
| ID | Pre-condition |
|---|---|
| P1 | Everflow S2S webhook received with valid provider_event_id, origin_user_id, and event_type (EV_MILESTONE_3_REFS or EV_MILESTONE_6_REFS) |
| P2 | provider_event_id has not previously been processed (idempotency gate not yet tripped) |
| P3 | origin_user_id resolves to a known user in the system |
| P4 | billing_country / billing_region of origin_user_id is not present in Compliance_geo_restrictions (sanctions clear) |
4. Post-conditions
| # | Post-condition |
|---|---|
| 1 | Idempotency lock record written: provider_event_id marked as processed in idempotency table |
| 2 | Promo code record inserted into promo_codes table: applicable_product_id = 'EVAL_L1', discount_percent = 100, is_used = false, origin_user_id set |
| 3 | Klaviyo Velocity_Reward_Unlocked transactional email dispatched to origin_user_id containing promo code |
| 4 | Discord message broadcast to #trading-floor-announcements via Redis queue |
| 5 | Audit entry written to bps_audit_logs |
| 6 | Audit entry replicated to S3 Glacier Object Lock (WORM) — 5-year + 1-month retention |
5. Basic Flow
Step 1 — Idempotency Gate
Middleware acquires a row-level lock before any write:
BEGIN;
SELECT id FROM idempotency_log
WHERE provider_event_id = :provider_event_id
FOR UPDATE;- If row exists: return
HTTP 200 OKwith{ "status": "duplicate", "skipped": true }— do not proceed. - If row does not exist: insert new row with
provider_event_id,origin_user_id,event_type,received_at; hold lock through commit at Step 3.
The UNIQUE index on provider_event_id provides a hard backstop against concurrent inserts.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine)
Step 2 — Sanctions Gate
SELECT 1 FROM Compliance_geo_restrictions
WHERE country_code = :billing_country
AND (region_code = :billing_region OR region_code IS NULL);- If row found: return
HTTP 403 Forbiddenwith{ "error": "SANCTIONS_BLOCKED", "user_id": :origin_user_id }. Abort flow. Write sanctions-block event tobps_audit_logs. - If row not found: proceed to Step 3.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine)
Step 3 — Promo Code Generation
INSERT INTO promo_codes (
code,
applicable_product_id,
discount_percent,
is_used,
origin_user_id,
event_type,
created_at
) VALUES (
:generated_code, -- cryptographically random alphanumeric, 16 chars
'EVAL_L1',
100,
false,
:origin_user_id,
:event_type,
NOW()
);
COMMIT; -- releases FOR UPDATE lock from Step 1generated_code is a 16-character cryptographically random alphanumeric string, unique by UNIQUE constraint on code column.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine)
Step 4 — Klaviyo Email
Middleware calls Klaviyo Transactional API:
- Template:
Velocity_Reward_Unlocked - To:
origin_user_idemail address - Payload fields:
promo_code,event_type,milestone_count(3 or 6),applicable_product(EVAL_L1)
On Klaviyo API failure: apply exponential backoff retry (see Exc-1). Do not block Discord dispatch if Klaviyo retries are in progress.
(Source: Zapier Integration V7.pdf §Flow 32)
Step 5 — Discord Broadcast
Enqueue Discord message to Redis queue for #trading-floor-announcements:
- Rate limit: 5 messages per 5 seconds (enforced by queue consumer).
- Implementation: native
fetch()only — axios is explicitly prohibited. - Message content: Announce milestone achievement for
origin_user_id(display name or anonymized handle per community policy).
Queue consumer dequeues and calls Discord Bot API within rate limit window.
(Source: Zapier Integration V7.pdf §Flow 32)
Step 6 — Audit Log
Write to bps_audit_logs:
{
"event_type": "VELOCITY_REWARD_ISSUED",
"origin_user_id": "<id>",
"provider_event_id": "<id>",
"milestone": 3,
"promo_code": "<code>",
"timestamp": "<ISO-8601>"
}Replicate to S3 Glacier Object Lock (WORM). Retention: 5 years + 1 month. First 2 years stored in immediately accessible tier. After 2 years: Glacier Deep Archive. Per CFTC/NFA record-keeping requirements.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine)
6. Alternative Flows
Alt-1 — Duplicate event (provider_event_id already processed)
- Idempotency gate detects existing row (Step 1).
- Return
HTTP 200 OK:{ "status": "duplicate", "skipped": true }. - No promo code generated. No email. No Discord. No audit event (event already logged at first processing).
- Flow ends cleanly.
7. Exceptional Flows
Exc-1 — Klaviyo API failure (Step 4)
Middleware applies exponential backoff:
delay(attempt) = 5s × 2^attempt
attempt 1 → 10s
attempt 2 → 20s
attempt 3 → 40sAfter 3 failed retries (total backoff window ~70s):
- Log
WARNentry inbps_audit_logs:{ "event": "KLAVIYO_RETRY_EXHAUSTED", "promo_code": "<code>", "origin_user_id": "<id>" }. - Send SEV-2 Slack alert to
#dev_ops:"⚠️ [SEV-2] Velocity Reward email failed after 3 retries for user <id>. Promo code <code> was issued but email not delivered. Manual resend required." - Discord broadcast (Step 5) proceeds regardless of Klaviyo failure — promo code was already committed.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine)
Exc-2 — Discord API failure (Step 5)
Redis queue consumer retries per Discord Bot rate-limit guidance. If Discord channel is unavailable (non-2xx after retries):
- Log
WARNinbps_audit_logs. - Slack alert to
#dev_ops:"⚠️ [SEV-2] Discord broadcast failed for Velocity Reward event <provider_event_id>.". - Non-blocking — promo code and email are unaffected.
Exc-3 — Sanctions block (Step 2)
- Return
HTTP 403 Forbidden. - Write sanctions-block record to
bps_audit_logswithevent_type = 'VELOCITY_REWARD_SANCTIONS_BLOCKED'. - No promo code generated. No email. No Discord.
- S3 Glacier replication of the block event still applies (CFTC/NFA audit trail).
Exc-4 — promo_codes INSERT collision (UNIQUE constraint on code)
- Middleware regenerates a new random
codeand retries INSERT (max 3 attempts). - If all 3 retries fail: return
HTTP 500, logERROR, SEV-2 Slack alert to#dev_ops. - Probability of collision with 16-char random alphanumeric is negligible but the guard must exist.
8. Business Rules
BR_9.14.1: Idempotency Is Mandatory — PostgreSQL FOR UPDATE + UNIQUE Index
Every incoming Everflow webhook MUST be checked for prior processing using SELECT ... FOR UPDATE on the idempotency table before any write. A UNIQUE constraint on provider_event_id provides the hard backstop. Duplicate events MUST return HTTP 200 OK silently — not HTTP 4xx — to prevent Everflow retry storms.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine)
BR_9.14.2: Sanctions Gate Executes Before Promo Code Generation
The Compliance_geo_restrictions check on billing_country/billing_region MUST run before any promo code is written to promo_codes. A sanctioned user must never receive a promo code or email, even if the Everflow milestone event is legitimate.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine)
BR_9.14.3: Promo Code Parameters Are Fixed
Every promo code issued by Flow 32 MUST have:
applicable_product_id = 'EVAL_L1'discount_percent = 100is_used = false(initial state)
No other product or discount value is valid for this flow.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine)
BR_9.14.4: Discord Implementation — native fetch() Only, No axios
All Discord API calls within Flow 32 MUST use the native fetch() API. axios is explicitly prohibited. This applies to all Discord Bot calls made from Middleware within this flow.
(Source: Zapier Integration V7.pdf §Flow 32)
BR_9.14.5: Discord Rate Limit — 5 Messages per 5 Seconds via Redis Queue
Discord broadcasts MUST be enqueued through Redis and consumed at a maximum rate of 5 messages per 5 seconds. Direct synchronous Discord calls that bypass the queue are prohibited to prevent API rate-limit bans.
(Source: Zapier Integration V7.pdf §Flow 32)
BR_9.14.6: CFTC/NFA Audit Trail — S3 Glacier WORM, 5-Year + 1-Month Retention
All audit events (reward issuance, sanctions blocks, retry exhaustion) MUST be written to bps_audit_logs and replicated to S3 Glacier Object Lock (WORM). Retention: 5 years + 1 month. First 2 years in immediately accessible tier. After 2 years: Glacier Deep Archive. Manual deletion is prohibited by Object Lock policy.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine)
BR_9.14.7: Exponential Backoff — Base 5s, 3 Retries, SEV-2 on Exhaustion
Third-party API failures (Klaviyo, Discord) MUST apply exponential backoff: delay = 5s × 2^attempt (10s, 20s, 40s). After 3 failed retries, dispatch SEV-2 Slack alert to #dev_ops. Flow does not crash — downstream steps that have already completed are not rolled back.
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine)
BR_9.14.8: Two Eligible Milestone Events Only
Only EV_MILESTONE_3_REFS and EV_MILESTONE_6_REFS are valid triggers for Flow 32. Any other event_type value received at POST /api/referral/velocity-reward MUST return HTTP 400 Bad Request and be logged as INVALID_EVENT_TYPE. Each milestone (3 and 6) issues exactly one single-use 100% EVAL_L1 code.
(Source: Zapier Integration V7.pdf §Flow 32; QnA_STAGE1_CHECKOUT_ONBOARDING.md STAGE1-064; UC_9.x_Client_Questions.md F32-01 — milestones 3/6 confirmed 2026-11-05)
9. Acceptance Criteria
| AC_ID | Flow | Given | Then | Type |
|---|---|---|---|---|
| AC-9.14-01 | Happy path — 3-referral milestone | Valid EV_MILESTONE_3_REFS webhook, sanctions clear, first occurrence | Promo code inserted (EVAL_L1, 100%); Velocity_Reward_Unlocked email sent; Discord broadcast queued; audit log written; HTTP 200 OK returned | Integration |
| AC-9.14-02 | Happy path — 6-referral milestone | Valid EV_MILESTONE_6_REFS webhook, sanctions clear, first occurrence | Same as AC-9.14-01; event_type = 'EV_MILESTONE_6_REFS' in promo_codes record | Integration |
| AC-9.14-03 | Idempotency — duplicate event | Same provider_event_id received twice | Second call returns HTTP 200 { "status": "duplicate", "skipped": true }; no second promo code; no second email | Integration |
| AC-9.14-04 | Concurrent duplicate | Two simultaneous requests with same provider_event_id | Exactly one promo code created; second transaction blocked by SELECT ... FOR UPDATE + UNIQUE constraint | Integration |
| AC-9.14-05 | Sanctions block | User's billing_country matches Compliance_geo_restrictions | HTTP 403 returned; no promo code; no email; sanctions-block event in bps_audit_logs | Integration |
| AC-9.14-06 | Klaviyo retry exhaustion | Klaviyo returns non-2xx for all 3 retry attempts | SEV-2 Slack alert to #dev_ops; promo code already committed remains valid; Discord broadcast still dispatched | Integration |
| AC-9.14-07 | Discord rate limit | 10 reward events arrive within 1 second | Redis queue consumers dispatch ≤ 5 messages per 5 s; no HTTP 429 from Discord | Integration |
| AC-9.14-08 | Discord uses native fetch | Discord call in Flow 32 implementation | No axios import present in the request path | Code review |
| AC-9.14-09 | Invalid event_type | Webhook with event_type = 'EV_UNKNOWN' | HTTP 400 Bad Request; INVALID_EVENT_TYPE logged; no promo code created | Integration |
| AC-9.14-10 | Audit WORM | Reward event processed successfully | bps_audit_logs row present; S3 Glacier Object Lock replication confirmed; Object Lock mode = COMPLIANCE | Integration |
10. Data Model
Table: promo_codes
| Column | Type | Constraints | Description |
|---|---|---|---|
id | BIGSERIAL | PK | Auto-increment primary key |
code | VARCHAR(32) | UNIQUE NOT NULL | 16-char cryptographically random alphanumeric |
applicable_product_id | VARCHAR(64) | NOT NULL | Fixed: 'EVAL_L1' for Flow 32 |
discount_percent | SMALLINT | NOT NULL | Fixed: 100 for Flow 32 |
is_used | BOOLEAN | NOT NULL DEFAULT false | Whether code has been redeemed |
origin_user_id | BIGINT | FK → users.id | Referrer who earned the reward |
event_type | VARCHAR(64) | NOT NULL | 'EV_MILESTONE_3_REFS' or 'EV_MILESTONE_6_REFS' |
created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | Code issuance timestamp |
used_at | TIMESTAMPTZ | NULLABLE | Timestamp of redemption |
Table: idempotency_log (logical — may be same as existing idempotency table)
| Column | Type | Constraints | Description |
|---|---|---|---|
id | BIGSERIAL | PK | — |
provider_event_id | VARCHAR(128) | UNIQUE NOT NULL | Everflow event ID — idempotency key |
origin_user_id | BIGINT | NOT NULL | — |
event_type | VARCHAR(64) | NOT NULL | — |
received_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | — |
11. API Reference
POST /api/referral/velocity-reward
| Field | Value |
|---|---|
| Method | POST |
| Auth | Everflow webhook HMAC signature (header: X-Everflow-Signature) |
| Caller | Everflow S2S webhook |
| Content-Type | application/json |
Request payload:
{
"provider_event_id": "ef_evt_abc123xyz",
"origin_user_id": 10042,
"event_type": "EV_MILESTONE_3_REFS",
"referral_count": 3,
"billing_country": "US",
"billing_region": "TX"
}Response — Happy path (HTTP 200 OK):
{
"status": "success",
"promo_code": "ABCD1234EFGH5678",
"applicable_product": "EVAL_L1",
"discount_percent": 100,
"email_dispatched": true,
"discord_queued": true
}Response — Duplicate event (HTTP 200 OK):
{
"status": "duplicate",
"skipped": true
}Response — Sanctions block (HTTP 403 Forbidden):
{
"error": "SANCTIONS_BLOCKED",
"user_id": 10042
}Response — Invalid event_type (HTTP 400 Bad Request):
{
"error": "INVALID_EVENT_TYPE",
"received": "EV_UNKNOWN",
"accepted": ["EV_MILESTONE_3_REFS", "EV_MILESTONE_6_REFS"]
}| Request Field | Type | Required | Description |
|---|---|---|---|
provider_event_id | STRING | Yes | Everflow's unique event ID — idempotency key |
origin_user_id | INT | Yes | Referrer user ID |
event_type | STRING | Yes | EV_MILESTONE_3_REFS or EV_MILESTONE_6_REFS |
referral_count | INT | Yes | Current referral milestone count (3 or 6) |
billing_country | STRING | Yes | ISO 3166-1 alpha-2 country code for sanctions check |
billing_region | STRING | No | ISO 3166-2 region/state code for sanctions check |
(Source: RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine)
12. Open Questions
| ID | Priority | Question | Assumption |
|---|---|---|---|
| Q1 | M | Should the Discord broadcast include the user's display name, a pseudonym, or remain fully anonymous? | BA assumption: anonymized handle or first name only; confirm with client for community policy |
| Q2 | L | Is idempotency_log a dedicated table for Flow 32 or the existing system-wide idempotency table? | BA assumption: system-wide table, filtered by event_type prefix EV_ |
| Q3 | M | What is the Klaviyo template ID / slug for Velocity_Reward_Unlocked? | Placeholder name used; confirm with Marketing/Klaviyo config |
| Q4 | L | Is S3 Glacier replication synchronous (within the request) or async (background job)? | BA assumption: async background job; audit log written synchronously, S3 replication queued |
13. Changelog
| Date | Version | Change | Previous | After | Source |
|---|---|---|---|---|---|
| 2026-11-05 | v1.1 | §1 Release row; BR_9.14.8 (one code per milestone + sources) | Release not stated; milestone model 3/6 vs pinned 2/6 + bundle unresolved (F32-01) | Release 2; milestones 3/6 confirmed, one EVAL_L1 code per milestone — bundle answer (F32-02) not applicable | Client sheet 2026-11-04 [BG-012]; F32-01 closed 2026-11-05; STAGE1-064 |
| 2026-10-01 | v1.0 | Initial document creation | — | Full spec: 4-step pipeline, idempotency gate, sanctions check, promo code generation, Klaviyo email, Discord broadcast, CFTC/NFA audit trail | RFQ_ Stack Trading Prop Tech V7.pdf §Velocity Reward Engine; Zapier Integration V7.pdf §Flow 32 |