Lesson 281: Construct February Fest Live Moderation Webhook Receipt on BUILD_RECEIPT (2026)

Direct answer: Before February 2027 live-week P0 Discord escalations, complete M1–M6—latch the listicle cousin receipt, lock the webhook URL outside git, pin p0_keyword_map_v1.json, send one synthetic test ping to #fest-ops, crosswalk facilitator_roster_v1.json, then file fest_moderation_webhook_receipt_v1.json and promote BUILD_RECEIPT fest_moderation_webhook_ok: true. Pair Construct webhook preflight (Guide #39), February moderation listicle (Blog), P0 webhook fix (Help #39), and cousin Lesson 267 (CSV ingest receipt).

Lesson hero for Construct February fest live moderation webhook BUILD_RECEIPT receipt

Why this matters now (February 2027 live week)

February 2027 live week hour one: AutoMod flags refund/crash/chargeback and facilitators paste templates into #playtest, but the pager lane is silent because the #fest-ops webhook still points at last month’s deleted channel or stale AutoMod rule IDs.

This lesson is the missing half of the discipline: Lesson 267 proves your CSV ingest scope is correct; 281 proves your live escalation webhook is correct and CI-safe through BUILD_RECEIPT promotion.

Beginner path (first safe webhook session)

Step Action Success check
1 Confirm listicle gates are GREEN (cousin receipt) fest_live_moderation_ok: true on BUILD_RECEIPT
2 Lock webhook URL in env/secret (not git) receipt stores secret ref only
3 Pin P0 keyword map p0_keyword_map_v1.json has >= 5 P0 terms
4 Send synthetic test ping message appears in #fest-ops < 60s
5 Crosswalk facilitator roster for today discord_oncall + mention role valid
6 File webhook receipt + promote BUILD_RECEIPT fest_moderation_webhook_ok: true

Time: ~45 minutes first setup; ~90 seconds nightly when gates are pinned.

Developer path (gates M1–M6)

Gate Check Fail when
M1 Cousin fest_live_moderation GREEN fest_live_moderation_ok: false on BUILD_RECEIPT
M2 Webhook URL lock outside git URL appears in plaintext in evidence
M3 p0_keyword_map_v1.json shape + rule IDs < 5 P0 terms or invalid/empty rule IDs
M4 Test ping lands in #fest-ops HTTP 401/404 or message missing
M5 Roster crosswalk for target fest day discord_oncall empty or role doesn’t resolve
M6 fest_moderation_webhook_receipt_v1.json + BUILD_RECEIPT latch receipt missing or BUILD_RECEIPT not promoted

Cousin receipt crosswalk (what you latch vs what you promote)

Field Cousin / Guide This lesson (281)
Schema fest_live_moderation_receipt_v1 fest_moderation_webhook_receipt_v1
Promotion fest_live_moderation_ok fest_moderation_webhook_ok
Scope templates + roster + slowmode live escalation webhook + P0 map + test ping
Cousins in receipt listicle + CSV ingest CSV ingest: playtest_csv_ingest_receipt_v1

Promotion order on BUILD_RECEIPT: fest_live_moderation_ok (M1) -> fest_moderation_webhook_ok (this lesson) -> pager lane.

M1 — latch listicle cousin receipt

jq -e '.fest_live_moderation_ok == true' release-evidence/BUILD_RECEIPT.json
jq -e '.schema == "fest_live_moderation_receipt_v1"' \
  release-evidence/ops/FEST_LIVE_MODERATION_RECEIPT.json
jq -e '(.surfaces | length) >= 1' release-evidence/ops/FEST_LIVE_MODERATION_RECEIPT.json

Fail-closed: complete the February moderation listicle four-tool minimum before webhook wiring—templates without roster create false confidence.

M2 — webhook URL lock (never commit tokens)

Do not paste Discord webhook URLs into Construct project files or public exports.

  1. Create webhook in Discord → Integrations → Webhooks → new webhook targeting #fest-ops
  2. Store URL in env/secret as DISCORD_FEST_OPS_WEBHOOK_URL
  3. Record only the secret ref in receipt (no raw URL)

Receipt fields shape:

"webhook_secret_ref": "env:DISCORD_FEST_OPS_WEBHOOK_URL",
"webhook_channel": "#fest-ops",
"webhook_last_rotated_utc": "2027-02-20T18:00:00Z"

PowerShell sanity (local):

if (-not $env:DISCORD_FEST_OPS_WEBHOOK_URL) { throw "M2: webhook env missing" }
$env:DISCORD_FEST_OPS_WEBHOOK_URL -match '^https://discord\.com/api/webhooks/\d+/'

Red flag: webhook URL appears in release-evidence/ plaintext—rotate and rerun M4.

M3 — pin p0_keyword_map_v1.json

Pin under release-evidence/ops/p0_keyword_map_v1.json.

{
  "schema": "p0_keyword_map_v1",
  "fest_window": "2027-02-next-fest-live",
  "terms": [
    { "keyword": "refund", "automod_rule_id": "123456789012345678", "severity": "P0" },
    { "keyword": "chargeback", "automod_rule_id": "123456789012345679", "severity": "P0" },
    { "keyword": "crash", "automod_rule_id": "123456789012345680", "severity": "P0" },
    { "keyword": "soft-lock", "automod_rule_id": "123456789012345681", "severity": "P0" },
    { "keyword": "data loss", "automod_rule_id": "123456789012345682", "severity": "P0" }
  ],
  "case_insensitive": true,
  "escalation_username": "Fest-P0-Escalation"
}

Fail-closed: AutoMod rule IDs missing → M3 fail.

Verify:

jq -e '.terms | length >= 5' release-evidence/ops/p0_keyword_map_v1.json
jq -e '.terms[] | select(.severity == "P0") | .keyword' \
  release-evidence/ops/p0_keyword_map_v1.json | sort -u | wc -l

M4 — synthetic test ping (curl)

Send a synthetic payload (not production player text):

curl -sS -o /dev/null -w "%{http_code}" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "Fest-P0-Escalation",
    "content": "[PREFLIGHT] P0 keyword map test — build_label=fest-demo-2027-02-rc1",
    "allowed_mentions": { "parse": [] }
  }' \
  "$DISCORD_FEST_OPS_WEBHOOK_URL"

Pass: HTTP 204 (Discord default) or 200; message visible in #fest-ops within 60 seconds.

Capture in receipt under test_ping.

M5 — facilitator roster crosswalk (facilitator_roster_v1.json)

ROSTER="release-evidence/ops/facilitator_roster_v1.json"
jq -e --arg day "D1" \
  '.facilitators[] | select(.fest_day == $day) | .discord_oncall' "$ROSTER"

Receipt crosswalk fields:

"roster_crosswalk": {
  "fest_day": "D1",
  "discord_oncall": "alex",
  "roster_sha256": "f4e2a1…",
  "mention_role_id": "987654321098765432",
  "shift_utc": "2027-02-22T14:00:00Z/2027-02-22T18:00:00Z"
}

Pass: discord_oncall exists for today’s fest day and the mention role id is valid for the Discord server.

M6 — file fest_moderation_webhook_receipt_v1.json + promote BUILD_RECEIPT

Minimal receipt skeleton:

{
  "schema": "fest_moderation_webhook_receipt_v1",
  "fest_window": "2027-02-next-fest-live",
  "build_label": "fest-demo-2027-02-rc1",
  "webhook_secret_ref": "env:DISCORD_FEST_OPS_WEBHOOK_URL",
  "webhook_channel": "#fest-ops",
  "p0_keyword_map_path": "release-evidence/ops/p0_keyword_map_v1.json",
  "roster_path": "release-evidence/ops/facilitator_roster_v1.json",
  "cousin_receipts": {
    "fest_live_moderation": "release-evidence/ops/FEST_LIVE_MODERATION_RECEIPT.json",
    "playtest_csv_ingest": "release-evidence/playtest/PLAYTEST_CSV_INGEST_RECEIPT.json"
  },
  "test_ping": {
    "http_status": 204,
    "sent_utc": "2027-02-21T11:30:00Z",
    "build_label": "fest-demo-2027-02-rc1"
  },
  "roster_crosswalk": {
    "fest_day": "D1",
    "discord_oncall": "alex",
    "roster_sha256": "f4e2a1…",
    "mention_role_id": "987654321098765432"
  },
  "fest_moderation_webhook_ok": true,
  "gates": {
    "M1_cousin_moderation": "pass",
    "M2_webhook_url_lock": "pass",
    "M3_p0_keyword_map": "pass",
    "M4_test_ping": "pass",
    "M5_roster_crosswalk": "pass",
    "M6_receipt": "pass"
  }
}

Then promote BUILD_RECEIPT (example keys from the P0 fix page):

"fest_moderation_webhook_ok": true,
"fest_mod_webhook_last_ping_utc": "2027-02-23T09:15:00Z"

Verification (fail-closed)

Check Pass when
Env resolves DISCORD_FEST_OPS_WEBHOOK_URL non-empty
Test ping HTTP 204/200 + message visible in #fest-ops
Keyword map >= 5 P0 terms with valid rule IDs
Roster discord_oncall non-empty for target fest day
Receipt fest_moderation_webhook_receipt_v1.json present
BUILD_RECEIPT fest_moderation_webhook_ok: true

If webhook test pings but pager is silent, open Help #39 and follow the recovery path (stale AutoMod rule mapping vs webhook URL vs roster drift).

Common mistakes

  1. Latching only the listicle receipt (M1) and skipping M4 test ping.
  2. Committing webhook URLs into repo or HTML exports.
  3. Keeping old AutoMod rule IDs in p0_keyword_map_v1.json after thread churn.
  4. Crosswalking roster for the wrong fest day (D1 vs D2).
  5. Assuming CSV ingest (Lesson 267) proves live webhook routing.

Next steps

  1. If you must patch behavior mid-fest, treat it as a webhook recovery incident and follow Help #39.
  2. For the next “live lane” step, complete Lesson 282 — hour-one funnel snapshot receipt so analytics and escalation do not drift.