{
  "name": "Pickedd v0 Website Builder",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "pickedd-v0-website",
        "responseMode": "responseNode",
        "options": {
          "rawBody": true
        }
      },
      "id": "6720ad38-ad89-413c-8b5d-55eff6e4e103",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        192
      ],
      "webhookId": "b0c0d0e0-0000-4000-8000-0000000000aa"
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\nconst j = item.json || {};\nconst headers = j.headers || {};\n\n// Exact raw request body. In production the Webhook \"Raw Body\" option provides it\n// as binary; fall back to json fields only for testing.\nlet rawBody;\nif (item.binary && item.binary.data && item.binary.data.data) {\n  rawBody = Buffer.from(item.binary.data.data, 'base64').toString('utf8');\n} else if (typeof j.rawBody === 'string') {\n  rawBody = j.rawBody;\n} else {\n  rawBody = JSON.stringify(j.body || {});\n}\n\nconst body = j.body || {};\nconst timestampHeader = headers['x-agentmarket-timestamp'];\nlet providedSig = headers['x-agentmarket-signature'] || '';\nprovidedSig = String(providedSig).replace(/^sha256=/, '');\n\nlet age = 999999;\nconst tsNum = parseInt(timestampHeader, 10);\nif (!isNaN(tsNum)) {\n  age = Math.floor(Date.now() / 1000) - tsNum;\n}\n\nconst signedValue = (timestampHeader != null ? String(timestampHeader) : '') + '.' + rawBody;\n\nreturn [{\n  json: {\n    order_id: body.order_id,\n    callback_url: body.callback_url,\n    instructions: (body.input && body.input.instructions) || '',\n    providedSig,\n    age,\n    signedValue,\n    rawBody,\n  },\n}];"
      },
      "id": "8ee1980f-a4de-49e7-b294-641e48ff3364",
      "name": "Parse Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        192
      ]
    },
    {
      "parameters": {
        "action": "hmac",
        "value": "={{ $json.signedValue }}",
        "dataPropertyName": "computedSig"
      },
      "id": "e1df8a25-8e77-4403-b01a-52a5ccc39a54",
      "name": "Compute HMAC",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 2,
      "position": [
        448,
        192
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "sig-match",
              "leftValue": "={{ $json.computedSig }}",
              "rightValue": "={{ $('Parse Request').item.json.providedSig }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "ts-fresh",
              "leftValue": "={{ $('Parse Request').item.json.age }}",
              "rightValue": 300,
              "operator": {
                "type": "number",
                "operation": "lte"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "21e45d3c-38f8-4850-9866-8142f17eecca",
      "name": "Signature Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        672,
        192
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "{\n  \"accepted\": true\n}",
        "options": {
          "responseCode": 202
        }
      },
      "id": "ecc0c332-a6c0-4e04-af5c-d7745579a084",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        896,
        96
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.v0.dev/v1/chats",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer PASTE_YOUR_V0_API_KEY"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ message: 'Build a modern, responsive, production-quality website for this client. Use only the information in the brief; do not invent prices, offers or contact details. Brief:\\n\\n' + $('Parse Request').item.json.instructions }) }}",
        "options": {
          "timeout": 300000
        }
      },
      "id": "b0c0d0e0-0000-4000-8000-000000000001",
      "name": "Generate Result",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1120,
        96
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "const j = $input.first().json || {};\nconst v = j.latestVersion || {};\nconst preview = v.demoUrl || j.demo || j.webUrl || j.url;\nconst editUrl = j.webUrl || j.url;\nconst files = (v.files || j.files || []).map(f => f.name || f.path).filter(Boolean);\nif (!preview) return [{ json: { error: 'v0 returned no preview link' } }];\nlet text = '## Your website is ready\\n\\n';\ntext += '**Live preview:** [Open your website](' + preview + ')\\n\\n';\nif (editUrl && editUrl !== preview) text += '**Project in v0:** [View the source](' + editUrl + ')\\n\\n';\nif (files.length) text += '### Files included\\n' + files.map(f => '- ' + f).join('\\n') + '\\n';\nreturn [{ json: { text } }];"
      },
      "id": "b0c0d0e0-0000-4000-8000-000000000002",
      "name": "Format v0 Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1340,
        -24
      ]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\nconst j = item.json || {};\n\nfunction nonEmptyStr(v) {\n  return (typeof v === 'string' && v.trim() !== '') ? v : null;\n}\n\n// Robust extraction: take the first candidate that exists and is a non-empty string.\nlet aiText = null;\nif (j.output && Array.isArray(j.output) && j.output[0] && Array.isArray(j.output[0].content) && j.output[0].content[0]) {\n  aiText = nonEmptyStr(j.output[0].content[0].text);\n}\nif (aiText == null) aiText = nonEmptyStr(j.text);\nif (aiText == null) aiText = nonEmptyStr(j.output);\nif (aiText == null && j.message) aiText = nonEmptyStr(j.message.content);\nif (aiText == null) aiText = nonEmptyStr(j.response);\n\nconst pv = $('Parse Request').first().json;\n// No usable text -> route to the failed callback instead of delivering an empty result.\nconst payloadObj = (aiText != null)\n  ? { order_id: pv.order_id, status: 'delivered', result: { text: aiText } }\n  : { order_id: pv.order_id, status: 'failed', error: 'Agent produced no text output' };\nconst bodyString = JSON.stringify(payloadObj);\nconst timestamp = Math.floor(Date.now() / 1000).toString();\n\nreturn [{\n  json: {\n    callback_url: pv.callback_url,\n    bodyString,\n    timestamp,\n    signedValue: timestamp + '.' + bodyString,\n  },\n}];"
      },
      "id": "1f3bb30d-c43d-4c79-bc5f-a92ef3361b30",
      "name": "Build Delivered Payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1344,
        0
      ]
    },
    {
      "parameters": {
        "action": "hmac",
        "value": "={{ $json.signedValue }}",
        "dataPropertyName": "signature"
      },
      "id": "c63164a7-a66f-4f2e-9bfb-50233511b108",
      "name": "Sign Callback",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 2,
      "position": [
        1568,
        96
      ]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\nconst pv = $('Parse Request').first().json;\n\nlet errMsg = 'AI generation failed';\nconst e = item.json.error;\nif (e) {\n  errMsg = (typeof e === 'string') ? e : (e.message || e.description || JSON.stringify(e));\n}\nerrMsg = String(errMsg).slice(0, 300);\n\nconst payloadObj = { order_id: pv.order_id, status: 'failed', error: errMsg };\nconst bodyString = JSON.stringify(payloadObj);\nconst timestamp = Math.floor(Date.now() / 1000).toString();\n\nreturn [{\n  json: {\n    callback_url: pv.callback_url,\n    bodyString,\n    timestamp,\n    signedValue: timestamp + '.' + bodyString,\n  },\n}];"
      },
      "id": "edc07f9d-a6dc-439f-8e6c-876e8aae5185",
      "name": "Build Failed Payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1344,
        192
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.callback_url }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-agentmarket-timestamp",
              "value": "={{ $json.timestamp }}"
            },
            {
              "name": "x-agentmarket-signature",
              "value": "=sha256={{ $json.signature }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ $json.bodyString }}",
        "options": {}
      },
      "id": "234ad1ff-dc7a-4114-95e0-a6e81f4c5268",
      "name": "Send Callback",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        1792,
        96
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "{\n  \"error\": \"invalid signature\"\n}",
        "options": {
          "responseCode": 401
        }
      },
      "id": "04b01b61-a6d0-4f15-9dff-99027a9a8c97",
      "name": "Respond 401",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        896,
        288
      ]
    },
    {
      "parameters": {
        "content": "## Webhook\nAfter publishing, copy the Production URL and paste it as the webhook address of your gig.",
        "height": 224,
        "width": 160,
        "color": 4
      },
      "id": "48d8044b-8f51-4fa3-a503-582d04a89480",
      "name": "Webhook note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -32,
        96
      ]
    },
    {
      "parameters": {
        "content": "## Verify signature\nDo not change. This checks the request really comes from Pickedd.",
        "height": 224,
        "width": 608,
        "color": 5
      },
      "id": "008a174a-8c65-4724-8748-40643ed97e8b",
      "name": "Verify signature note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        192,
        96
      ]
    },
    {
      "parameters": {
        "content": "## Crypto credential\nCreate a Crypto credential and paste your webhook secret from your Pickedd dashboard. Use the same credential in both Crypto nodes.",
        "height": 224,
        "width": 160,
        "color": 3
      },
      "id": "57933433-7435-49c2-a133-b036a5e66042",
      "name": "Crypto note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        416,
        96
      ]
    },
    {
      "parameters": {
        "content": "## v0\nPaste your v0 API key in the Generate Result node (Authorization header: Bearer YOUR_KEY). v0 builds the site and the preview link goes to the buyer.",
        "height": 224,
        "width": 160,
        "color": 6
      },
      "id": "df9b769b-d465-4b9a-b822-cb40440b0676",
      "name": "AI note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1088,
        0
      ]
    }
  ],
  "pinData": {},
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Parse Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Request": {
      "main": [
        [
          {
            "node": "Compute HMAC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute HMAC": {
      "main": [
        [
          {
            "node": "Signature Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Signature Valid?": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond 401",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond to Webhook": {
      "main": [
        [
          {
            "node": "Generate Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Result": {
      "main": [
        [
          {
            "node": "Format v0 Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Failed Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Delivered Payload": {
      "main": [
        [
          {
            "node": "Sign Callback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sign Callback": {
      "main": [
        [
          {
            "node": "Send Callback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Failed Payload": {
      "main": [
        [
          {
            "node": "Sign Callback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format v0 Result": {
      "main": [
        [
          {
            "node": "Build Delivered Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "4b632e31-3194-4ff2-ad90-ff5687dffcb6",
  "meta": {
    "instanceId": "eb7c1d4a4364f90fb49fdb46e09044e3d98759ea6181af53bf153bd8a75ff923"
  },
  "nodeGroups": [
    {
      "id": "6b69ab8d-7df5-48ac-8562-44987560d80d",
      "name": "Verify signature",
      "nodeIds": [
        "8ee1980f-a4de-49e7-b294-641e48ff3364",
        "e1df8a25-8e77-4403-b01a-52a5ccc39a54",
        "21e45d3c-38f8-4850-9866-8142f17eecca"
      ],
      "description": "Reads the timestamp + signature headers and recomputes the HMAC over the raw body to decide whether the request is authentic and fresh (<=300s)."
    },
    {
      "id": "248f0ea2-71e7-4079-b6cb-de1f40652535",
      "name": "Deliver result",
      "nodeIds": [
        "ecc0c332-a6c0-4e04-af5c-d7745579a084",
        "377a3747-6493-4004-aac9-07de923b14c1",
        "1f3bb30d-c43d-4c79-bc5f-a92ef3361b30",
        "edc07f9d-a6dc-439f-8e6c-876e8aae5185",
        "c63164a7-a66f-4f2e-9bfb-50233511b108",
        "234ad1ff-dc7a-4114-95e0-a6e81f4c5268"
      ],
      "description": "Returns 202, generates the answer from the request instructions, then posts a signed callback with the result or a failure message."
    }
  ],
  "id": "2kApAmgWuMSG0Auu",
  "tags": []
}