{
  "name": "Workflow F \u2014 Scheduled One-Off Surge Events",
  "nodes": [
    {
      "parameters": {
        "content": "## Workflow F \u2014 Scheduled One-Off Surge Events\n\n**Trigger:** POST /webhook/schedule-surge\n\n**Purpose:** Accepts a specific future date/time for a surge test (up to 15 days ahead), validates it, saves to the scheduled_surges table in Supabase, and confirms in Discord. Workflow G polls every minute and fires when due.\n\n**Flow:**\nWebhook (POST /schedule-surge) \u2192 Validate schedule (Code \u2014 checks ISO date, in future, within 15 days) \u2192 Save surge event (Postgres insert, status=pending) \u2192 Confirm Discord \u2192 Respond { scheduled: true, scheduledAt, label, delayMinutes }\n\n**Payload format:**\n```json\n{\n  \"scheduledAt\": \"2026-06-15T14:00:00+10:00\",\n  \"label\": \"itch new release day\"\n}\n```\n\n**Called by:** Dashboard Control page \u2192 Surge Event Scheduler card \u2192 datetime picker + label \u2192 Schedule surge button \u2192 api.js scheduleSurge() \u2192 this webhook.\n\n**Use case:** Schedule a surge test to fire exactly when itch.io traffic is naturally high (new game release, Reddit post, influencer mention) to get real-world load test data at peak organic traffic."
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -200,
        100
      ],
      "id": "sticky-f",
      "name": "Sticky Note \u2014 Workflow F"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "schedule-surge",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "f967bdf3-a1b0-4b21-abed-360a888d011a",
      "name": "Schedule surge webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const { scheduledAt, label } = $input.first().json.body;\nif (!scheduledAt) throw new Error('scheduledAt is required (ISO 8601)');\n\nconst fireAt = new Date(scheduledAt);\nconst now = new Date();\nconst delayMs = fireAt.getTime() - now.getTime();\n\nif (delayMs < 0) throw new Error('scheduledAt must be in the future');\nif (delayMs > 15 * 24 * 60 * 60 * 1000) throw new Error('scheduledAt must be within 15 days');\n\nreturn [{ json: {\n  scheduledAt: fireAt.toISOString(),\n  label: label || 'Scheduled surge',\n  delayMs,\n  delayMinutes: Math.round(delayMs / 60000)\n} }];"
      },
      "id": "3a4f0881-4a29-4c00-a971-b459f7a1dd8c",
      "name": "Validate schedule",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "schema": {
          "__rl": true,
          "value": "public",
          "mode": "list"
        },
        "table": {
          "__rl": true,
          "value": "scheduled_surges",
          "mode": "list",
          "cachedResultName": "scheduled_surges"
        },
        "dataMode": "defineBelow",
        "valuesToSend": {
          "values": [
            {
              "column": "scheduled_at",
              "value": "={{ $json.scheduledAt }}"
            },
            {
              "column": "label",
              "value": "={{ $json.label }}"
            },
            {
              "column": "status",
              "value": "pending"
            },
            {
              "column": "created_at",
              "value": "={{ new Date().toISOString() }}"
            }
          ]
        },
        "options": {}
      },
      "id": "7dd5f21e-a09a-472a-b977-6e3b659e1935",
      "name": "Save surge event",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "resource": "message",
        "guildId": {
          "__rl": true,
          "value": "1066890817425387581",
          "mode": "id"
        },
        "channelId": {
          "__rl": true,
          "value": "1514424075282550916",
          "mode": "list",
          "cachedResultName": "gamers-lab-monitor"
        },
        "content": "=\ud83d\udcc5 **Surge event scheduled**\n\n**Label:** {{ $json.label }}\n**Fires at:** {{ $json.scheduledAt }}\n**In:** {{ $json.delayMinutes }} minutes",
        "options": {}
      },
      "id": "5a945844-69b3-4d26-86af-207859b3de71",
      "name": "Confirm Discord",
      "type": "n8n-nodes-base.discord",
      "typeVersion": 2,
      "position": [
        900,
        300
      ],
      "credentials": {
        "discordBotApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ scheduled: true, scheduledAt: $json.scheduledAt, label: $json.label, delayMinutes: $json.delayMinutes }) }}",
        "options": {}
      },
      "id": "8160f1b8-5b29-4444-9dd9-6e1c314dcbfc",
      "name": "Respond2",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        1100,
        300
      ]
    }
  ],
  "connections": {
    "Schedule surge webhook": {
      "main": [
        [
          {
            "node": "Validate schedule",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate schedule": {
      "main": [
        [
          {
            "node": "Save surge event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save surge event": {
      "main": [
        [
          {
            "node": "Confirm Discord",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Confirm Discord": {
      "main": [
        [
          {
            "node": "Respond2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}