{
  "id": "pdfops-onboarding-packet-merge",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "Generate onboarding packets from two PDF forms and upload to Google Drive",
  "nodes": [
    {
      "id": "note-overview",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -800,
        -180
      ],
      "parameters": {
        "width": 560,
        "height": 1260,
        "content": "## Generate onboarding packets from two PDF forms and upload to Google Drive\n\nTurn a single webhook call (new hire, new tenant) into a finished onboarding packet: two blank PDF forms filled, merged and uploaded to Google Drive.\n\n### How it works\n- **New Record Submitted** receives `formAUrl`/`formBUrl` (https URLs to two blank fillable PDFs), `fieldsA`/`fieldsB` and `packetName`.\n- **Payload Valid?** rejects anything without two https URLs and two field objects, before a single fetch happens.\n- Each blank is fetched and filled by its own **PDFops Fill Form** node.\n- **Combine Filled Forms** (core Merge node) waits for both branches; Form A is always page 1.\n- **Both Forms Filled?** refuses to build a packet unless both PDFs arrived, so a half packet is never uploaded.\n- **PDFops Merge Packet** joins the two PDFs, **Upload Packet to Drive** stores it, and the caller gets JSON \u2014 400 on any failure, not a misleading 200.\n\n### Setup\n1. Install the **PDFops** community node (`n8n-nodes-pdfops`) \u2014 verified: one-click install on n8n Cloud (search \"PDFops\" in the nodes panel), or Settings \u2192 Community Nodes on self-hosted.\n2. *(Optional)* Add a **PDFops API** credential for a higher quota \u2014 keyless works at 100 req/IP/month.\n3. Connect a **Google Drive** credential on the upload node.\n4. Field values must be **strings** \u2014 `{\"salary\": \"50000\"}`, not `50000`.\n5. The webhook is public as shipped. Add header auth before production: it fetches whatever URLs it is sent.\n\n### Customization tips\n- Run PDFops **Inspect Fields** on each blank once to get exact field names.\n- Add a third form by duplicating a fetch \u2192 fill branch and raising Merge inputs to 3.\n- Swapping Drive for S3 or email? Use `.first()`, not `.item`, after the merge."
      },
      "typeVersion": 1
    },
    {
      "id": "note-section-1",
      "name": "Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -160,
        -40
      ],
      "parameters": {
        "color": 7,
        "width": 800,
        "height": 700,
        "content": "## 1. Receive the record & fetch both blanks\nOne webhook call carries URLs and field values for both forms; the HTTP nodes download the blank PDFs."
      },
      "typeVersion": 1
    },
    {
      "id": "note-section-2",
      "name": "Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        660,
        -40
      ],
      "parameters": {
        "color": 7,
        "width": 440,
        "height": 700,
        "content": "## 2. Fill both forms\nEach PDFops node fills one form. n8n runs the branches one after the other, then the Merge node waits for both."
      },
      "typeVersion": 1
    },
    {
      "id": "note-section-3",
      "name": "Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1120,
        -40
      ],
      "parameters": {
        "color": 7,
        "width": 900,
        "height": 700,
        "content": "## 3. Verify, merge, upload, answer\nOnly a complete pair becomes a packet. PDFops joins the PDFs, Drive stores it, and the caller always gets a status code that means something."
      },
      "typeVersion": 1
    },
    {
      "id": "trigger-webhook",
      "name": "New Record Submitted",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -80,
        310
      ],
      "parameters": {
        "path": "onboarding-packet-merge",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "http-fetch-a",
      "name": "Fetch Blank Form A",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        400,
        180
      ],
      "parameters": {
        "url": "={{ $json.body.formAUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "http-fetch-b",
      "name": "Fetch Blank Form B",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        400,
        440
      ],
      "parameters": {
        "url": "={{ $json.body.formBUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "pdfops-fill-a",
      "name": "PDFops Fill Form A",
      "type": "n8n-nodes-pdfops.pdfops",
      "onError": "continueRegularOutput",
      "position": [
        660,
        180
      ],
      "parameters": {
        "fields": "={{ $('New Record Submitted').item.json.body.fieldsA }}",
        "fileName": "form-a-filled.pdf",
        "operation": "fill",
        "binaryPropertyName": "data",
        "outputBinaryPropertyName": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "pdfops-fill-b",
      "name": "PDFops Fill Form B",
      "type": "n8n-nodes-pdfops.pdfops",
      "onError": "continueRegularOutput",
      "position": [
        660,
        440
      ],
      "parameters": {
        "fields": "={{ $('New Record Submitted').item.json.body.fieldsB }}",
        "fileName": "form-b-filled.pdf",
        "operation": "fill",
        "binaryPropertyName": "data",
        "outputBinaryPropertyName": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "combine-filled-forms",
      "name": "Combine Filled Forms",
      "type": "n8n-nodes-base.merge",
      "position": [
        920,
        310
      ],
      "parameters": {
        "mode": "append",
        "numberInputs": 2
      },
      "typeVersion": 3.2
    },
    {
      "id": "pdfops-merge",
      "name": "PDFops Merge Packet",
      "type": "n8n-nodes-pdfops.pdfops",
      "onError": "continueErrorOutput",
      "position": [
        1360,
        310
      ],
      "parameters": {
        "fileName": "={{ $('New Record Submitted').first().json.body.packetName || 'onboarding-packet.pdf' }}",
        "operation": "merge",
        "binaryPropertyName": "data",
        "outputBinaryPropertyName": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "gdrive-upload",
      "name": "Upload Packet to Drive",
      "type": "n8n-nodes-base.googleDrive",
      "onError": "continueErrorOutput",
      "position": [
        1580,
        310
      ],
      "parameters": {
        "name": "={{ $('New Record Submitted').first().json.body.packetName || 'onboarding-packet.pdf' }}",
        "driveId": {
          "mode": "list",
          "value": "My Drive",
          "cachedResultName": "My Drive"
        },
        "options": {},
        "folderId": {
          "mode": "list",
          "value": "root",
          "cachedResultName": "/ (Root folder)"
        },
        "resource": "file",
        "operation": "upload",
        "inputDataFieldName": "data"
      },
      "typeVersion": 3
    },
    {
      "id": "if-payload-valid",
      "name": "Payload Valid?",
      "type": "n8n-nodes-base.if",
      "position": [
        140,
        310
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-payload",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ /^https:\\/\\//.test($json.body.formAUrl || '') && /^https:\\/\\//.test($json.body.formBUrl || '') && typeof $json.body.fieldsA === 'object' && $json.body.fieldsA !== null && !Array.isArray($json.body.fieldsA) && typeof $json.body.fieldsB === 'object' && $json.body.fieldsB !== null && !Array.isArray($json.body.fieldsB) }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "note-section-4",
      "name": "Section 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1080,
        700
      ],
      "parameters": {
        "color": 7,
        "width": 520,
        "height": 280,
        "content": "## Failure path\nInvalid payloads, a form that won't fetch or fill, and upload errors all answer the caller here \u2014 a half packet is never uploaded."
      },
      "typeVersion": 1
    },
    {
      "id": "if-both-forms",
      "name": "Both Forms Filled?",
      "type": "n8n-nodes-base.if",
      "position": [
        1140,
        310
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-both-forms",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $input.all().filter(i => i.binary && i.binary.data).length === 2 }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "respond-ok",
      "name": "Respond Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1800,
        310
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'ok', packet: $('New Record Submitted').first().json.body.packetName || 'onboarding-packet.pdf' }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "respond-err",
      "name": "Respond With Error",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1140,
        820
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'error', reason: ($json.error ? 'step_failed' : ($json.body ? 'invalid_payload' : 'incomplete_packet')), message: ($json.error ? ($json.error.message || String($json.error)) : 'Both form URLs must be https and fieldsA/fieldsB must be objects; a packet is only built when both forms fill successfully.') }) }}"
      },
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Payload Valid?": {
      "main": [
        [
          {
            "node": "Fetch Blank Form A",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Blank Form B",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond With Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Both Forms Filled?": {
      "main": [
        [
          {
            "node": "PDFops Merge Packet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond With Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Blank Form A": {
      "main": [
        [
          {
            "node": "PDFops Fill Form A",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Blank Form B": {
      "main": [
        [
          {
            "node": "PDFops Fill Form B",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDFops Fill Form A": {
      "main": [
        [
          {
            "node": "Combine Filled Forms",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDFops Fill Form B": {
      "main": [
        [
          {
            "node": "Combine Filled Forms",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "PDFops Merge Packet": {
      "main": [
        [
          {
            "node": "Upload Packet to Drive",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond With Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Filled Forms": {
      "main": [
        [
          {
            "node": "Both Forms Filled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New Record Submitted": {
      "main": [
        [
          {
            "node": "Payload Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Packet to Drive": {
      "main": [
        [
          {
            "node": "Respond Success",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond With Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}