Create and Send Pennylane Invoices from Webhook Data

Overview

This workflow provides a secure, production-ready webhook endpoint that automatically creates finalized invoices in Pennylane and optionally sends them by email to the customer. It is designed for SMEs, freelancers, and any business using Pennylane who want to automate invoice creation from external systems such as CRMs, web forms, or custom applications.

By exposing a single POST endpoint with Basic HTTP authentication, the workflow validates the incoming payload, looks up or creates the customer in Pennylane, constructs an invoice with line items, VAT, and due date, finalizes it (non-draft), and—if requested—emails it to the customer with automatic retry logic. The webhook always returns a structured JSON response with invoice details and email delivery status.

Workflow Steps

  1. WH Receive Invoice Data (Webhook node) — Accepts POST requests at path /pennylane-invoice. Authentication: HTTP Basic Auth (user must provide a credential with username and password). Validates that incoming data is well-formed.
  2. Code Validate Payload (Code node) — Runs JavaScript to validate required fields (customer_name, customer_email, items[] with label, quantity, unit_price). It applies sensible defaults (country: FR, currency: EUR, VAT rate: FR_200, due date: +30 days) and throws an error if validation fails.
  3. PL Search Customer (HTTP Request node) — Makes a GET request to Pennylane API GET /api/external/v2/customers with a query filter by email. Authentication: HTTP Header Auth (Bearer token). Looks for an existing customer by email.
  4. IF Customer Exists (IF node) — Checks if the search returned any customer records. If yes (output 0), goes to Set Customer ID; if no (output 1), goes to PL Create Customer.
  5. PL Create Customer (HTTP Request node) — POST to POST /api/external/v2/company_customers with customer name, email, and billing address. Auth: HTTP Header Auth.
  6. Set Customer ID (Set node) — Sets customer_id from either the existing customer ID (from search) or the newly created customer ID. Uses expression {{ $json.id ?? $json.items?.[0]?.id }}.
  7. Code Build Invoice Payload (Code node) — Builds the invoice payload object using customer ID, line items, dates, and VAT rates. Converts unit_price to a formatted string (e.g., "1500.00").
  8. PL Create Invoice (HTTP Request node) — POST to POST /api/external/v2/customer_invoices with the constructed payload. Auth: HTTP Header Auth. Creates a finalized invoice (draft: false).
  9. IF Send Email (IF node) — Checks the send_email boolean from the original payload. If true, proceeds to email sending path; if false, goes directly to Set Output Response.
  10. Set Email Attempt (Set node) — Increments an attempt counter for email retry logic.
  11. Wait PDF Generation (Wait node) — Pauses for 1 minute to allow Pennylane to generate the PDF of the invoice.
  12. PL Send Invoice Email (HTTP Request node) — POST to POST /api/external/v2/customer_invoices/{id}/send_by_email. Auth: HTTP Header Auth. Configured with neverError: true to capture full response and avoid throwing.
  13. IF Email Sent (IF node) — Checks if the HTTP status code is 204 (success). If yes, goes to Set Output Response; if no (e.g., 409 conflict), goes to Code Evaluate Email Retry.
  14. Code Evaluate Email Retry (Code node) — Evaluates whether the email should be retried (409 status and attempt count < 5). Returns a retry boolean.
  15. IF Can Retry Email (IF node) — If retry is true, loops back to Set Email Attempt and repeats the wait/send cycle. If false, goes to Set Email Delivery Failed.
  16. Set Email Delivery Failed (Set node) — Constructs a response object indicating invoice creation succeeded but email delivery did not (with status code and message).
  17. Set Output Response (Set node) — Final node that assembles the webhook response JSON with success, invoice_id, invoice_number, amount, customer, public_url, and email_sent.

Setup Instructions

  1. Pennylane API Token: Log in to your Pennylane account, go to Settings > API Tokens, and create a token with at least customer_invoices:all and customers:all scopes. Copy the token.
  2. n8n Credentials:
    • Create an HTTP Basic Auth credential for the webhook. Use any username/password you like—this protects your endpoint.
    • Create an HTTP Header Auth credential for Pennylane. Set the header name to Authorization and the value to Bearer <YOUR_PENNYLANE_TOKEN>.
  3. Configure the Webhook: In the WH Receive Invoice Data node, select your Basic Auth credential. The production webhook URL will be shown when the workflow is activated.
  4. Set Invoice Numbering: Ensure sequential invoice numbering is enabled in your Pennylane settings.
  5. Test Without Email: Send a test payload with "send_email": false to verify invoice creation before enabling customer email delivery.
  6. Activate: Publish the workflow and use the provided webhook URL in your external system.

Use Cases & Variations

  • CRM Integration: Trigger this workflow from a CRM (e.g., HubSpot, Salesforce) when a deal is won to automatically generate and send an invoice.
  • Form Submission: Use a web form tool like Typeform or Gravity Forms to collect invoice data and POST it to this endpoint.
  • Custom Automation: Replace the webhook trigger with a schedule or a database listener to batch-create invoices.
  • Multi-currency: Modify the payload to accept different currencies by changing the currency default.
  • Draft Mode: Set draft: true in the invoice payload to create drafts for manual review before sending.
  • Error Handling: Configure an n8n Error Workflow to capture and notify on any exceptions.
25 nodeswebhook triggerFinance
Sticky NoteWebhookCodeHTTP RequestIFSetWait

Workflow JSON

{
  "name": "Create and send Pennylane invoices from webhook data",
  "nodes": [
    {
      "parameters": {
        "content": "# Create and send Pennylane invoices from webhook data\n\n## Who is this for\n\nSMEs and freelancers using Pennylane who want to create invoices automatically from a CRM, form, or business application.\n\n## What it does\n\nA protected POST webhook validates customer and line-item data, finds or creates the customer in Pennylane, creates a finalized invoice, optionally emails it through Pennylane, and returns the invoice details to the caller.\n\n## Security and setup\n\n1. Create an **HTTP Basic Auth** credential and select it on `WH Receive Invoice Data`. Never expose an unauthenticated invoicing webhook.\n2. Create an **HTTP Header Auth** credential for Pennylane: name `Authorization`, value `Bearer <YOUR_PENNYLANE_TOKEN>`.\n3. Select that Pennylane credential on all four `PL` HTTP Request nodes.\n4. Configure sequential invoice numbering in Pennylane.\n5. Test with `send_email: false` before enabling customer delivery. Email delivery retries Pennylane HTTP 409 responses once per minute, up to five attempts.\n6. Activate the workflow and use its production webhook URL.\n\nRequires n8n 1.40+ and a Pennylane API token with `customer_invoices:all` and `customers:all` scopes. Import verified with n8n 2.31.5.\n\nSource and full payload documentation: https://github.com/Gauthier-Huguenin/n8n-pennylane-auto-invoicing\n\nAdapted from the original template © 2026 Gauthier Huguenin. Distributed under the MIT License; the complete notice is included in this workflow.\n\n---\n\n### Flow\n\n`WH Receive Invoice Data`\n  → `Code Validate Payload`\n  → `PL Search Customer`\n  → `IF Customer Exists`\n    → yes: `Set Customer ID`\n    → no: `PL Create Customer` → `Set Customer ID`\n  → `Code Build Invoice Payload`\n  → `PL Create Invoice`\n  → `IF Send Email`\n    → yes: retry `Wait PDF Generation` → `PL Send Invoice Email` up to five times on HTTP 409\n    → no: `Set Output Response`\n  → `Set Output Response`",
        "height": 1376,
        "width": 720
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -592,
        -272
      ],
      "typeVersion": 1,
      "id": "2432a919-ea99-4529-969d-2f94a9f44110",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "pennylane-invoice",
        "responseMode": "lastNode",
        "options": {},
        "authentication": "basicAuth"
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        272,
        32
      ],
      "id": "402c4d14-ab30-4baf-85b5-daec4ceb06f4",
      "name": "WH Receive Invoice Data"
    },
    {
      "parameters": {
        "jsCode": "const webhookItem = $input.first().json;\nconst input = webhookItem.body ?? webhookItem;\nconst errors = [];\n\nif (typeof input.customer_name !== 'string' || input.customer_name.trim() === '') {\n  errors.push('customer_name is required');\n}\nif (typeof input.customer_email !== 'string' || !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(input.customer_email)) {\n  errors.push('customer_email is required and must be valid');\n}\nif (!Array.isArray(input.items) || input.items.length === 0) {\n  errors.push('items array is required and must contain at least one item');\n} else {\n  input.items.forEach((item, i) => {\n    if (typeof item.label !== 'string' || item.label.trim() === '') errors.push(`items[${i}].label is required`);\n    if (typeof item.quantity !== 'number' || !Number.isFinite(item.quantity) || item.quantity <= 0) errors.push(`items[${i}].quantity must be a number > 0`);\n    if (typeof item.unit_price !== 'number' || !Number.isFinite(item.unit_price) || item.unit_price <= 0) errors.push(`items[${i}].unit_price must be a number > 0`);\n  });\n}\nif (input.customer_country && !/^[A-Z]{2}$/.test(input.customer_country)) {\n  errors.push('customer_country must be an uppercase ISO alpha-2 code');\n}\nif (input.due_date && !/^\\d{4}-\\d{2}-\\d{2}$/.test(input.due_date)) {\n  errors.push('due_date must use YYYY-MM-DD format');\n}\nif (errors.length > 0) throw new Error('Validation failed: ' + errors.join('; '));\n\nreturn [{ json: {\n  customer_name: input.customer_name.trim(),\n  customer_email: input.customer_email.trim().toLowerCase(),\n  customer_address: typeof input.customer_address === 'string' ? input.customer_address : '',\n  customer_postal_code: typeof input.customer_postal_code === 'string' ? input.customer_postal_code : '00000',\n  customer_city: typeof input.customer_city === 'string' ? input.customer_city : 'Non renseigné',\n  customer_country: input.customer_country || 'FR',\n  items: input.items.map(item => ({\n    label: item.label.trim(),\n    quantity: item.quantity,\n    unit_price: item.unit_price,\n    vat_rate: item.vat_rate || 'FR_200'\n  })),\n  due_date: input.due_date || new Date(Date.now() + 30 * 86400000).toISOString().split('T')[0],\n  currency: input.currency || 'EUR',\n  reference: input.reference || null,\n  send_email: input.send_email === true\n} }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        528,
        32
      ],
      "id": "585218e8-449a-40da-8ce7-d8157998c2f7",
      "name": "Code Validate Payload"
    },
    {
      "parameters": {
// ... truncated (copy to see full JSON)

How to Import This Workflow

  1. 1Copy the workflow JSON above using the Copy Workflow JSON button.
  2. 2Open your n8n instance and go to Workflows.
  3. 3Click Import from JSON and paste the copied workflow.

Don't have an n8n instance? Start your free trial at n8nautomation.cloud

Related Templates

AI-Powered Auto-Healing for Failed n8n Workflows

High-Level Summary This workflow automates the entire overdue invoice recovery process using an AI agent. It runs daily on a schedule, reads unpaid invoices from a Google Sheet, and for each invoice uses OpenAI's GPT-4o-mini to analyze the situation and generate a personalized, context-aware recovery email. The email is sent via Gmail, and if the AI determines the account requires escalation (legal or collections), an internal alert is emailed to your legal team. The workflow then updates the invoice sheet with the new escalation level and appends a detailed log row to a separate recovery sheet for full auditability. Slack alerts notify the team if no invoices are found or if the AI encounters an error. Nodes and Their Functions Below is every node in the workflow, listed in execution order, with its type, authentication method, and key configuration. Daily Invoice Check (Schedule Trigger, No auth) — Fires the workflow every 24 hours. Configured with an interval rule of 24 hours. Read Overdue Invoices (Google Sheets, OAuth2) — Reads all rows from the "invoices" tab of your Google Sheet. Requires a Google Sheets OAuth2 credential and the spreadsheet ID. Any Overdue Invoices? (If, No auth) — Checks if the field is not empty. If invoices exist, the workflow proceeds to the AI agent; otherwise, it triggers a Slack alert. Slack: No Invoices Found Alert (Slack, OAuth2) — Sends a message to a specified Slack channel when no overdue invoices are found. Uses Slack OAuth2 credential. AI Recovery Agent (LangChain Agent, uses OpenAI API key via sub-node) — The core AI node. It receives each invoice row and a system prompt instructing it to act as an AR recovery specialist. It outputs a JSON object with escalation level, subject, email body, payment plans, and escalation action. The agent is connected to the GPT-4o-mini Language Model node. GPT-4o-mini Language Model (OpenAI Chat Model, API Key) — Provides the language model for the AI agent. Uses the model. Requires an OpenAI API key credential. Parse & Structure AI Response (Code, No auth) — A JavaScript code node that cleans and parses the AI's raw output into a structured JSON object. It also merges the original invoice data (invoice number, client name, etc.) into the output. Send Recovery Email to Client (Gmail, OAuth2) — Sends the personalized recovery email to the client. Uses the and from the AI response. The recipient email is taken from the original invoice row. Requires a Gmail OAuth2 credential. Needs Escalation? (If, No auth) — Checks if the field from the AI response is not "none". If escalation is needed, the workflow sends an internal alert; otherwise, it skips directly to logging. Send Escalation Alert to Legal Team (Gmail, OAuth2) — Sends an HTML email to a predefined legal team address with details about the high-risk invoice. The email includes client name, invoice number, amount due, days overdue, escalation level, and AI summary. Requires a Gmail OAuth2 credential. Update Invoice Status in Sheet (Google Sheets, OAuth2) — Updates the original invoice row in the "invoices" tab with the new escalation level and AI summary. Uses the row number to match the correct row. Append Row to Recovery Log (Google Sheets, OAuth2) — Appends a new row to the "Recovery sheet" tab with a full log of the action taken, including invoice details, AI subject, summary, escalation level, and Gmail label IDs. Slack: AI Error Alert (Slack, OAuth2) — Triggered if the AI Recovery Agent node encounters an error. Sends a Slack message with the invoice number and a request to check execution logs. Additionally, there are several Sticky Notes (Overview, Section: Trigger, Section: AI Agent, Section: Email, Section: Logging, Section: Security) that serve as documentation and visual guides within the editor. Setup Instructions To use this workflow, you need the following accounts and credentials: Google Sheets — A spreadsheet with two tabs: - : Columns should include Invoice, Client, Email, Amount, Due Date, Days Overdue, Level, Reminders Sent, and optionally row_number. - : Columns for logging (Invoice, Client, Amount, Days Overdue, Level, Email, escalation_level, summary, subject, labelIds). Create a Google Sheets OAuth2 credential in n8n and connect it to both Google Sheets nodes. Gmail — A Gmail account for sending recovery emails and escalation alerts. Create a Gmail OAuth2 credential in n8n. Update the escalation email recipient address () to your legal or collections team's email. OpenAI — An OpenAI API key with access to the model. Create an OpenAI API credential in n8n. Slack — A Slack workspace and a channel for alerts. Create a Slack OAuth2 credential in n8n. Replace with the actual channel ID. After connecting credentials, update the Google Sheet ID in the "Read Overdue Invoices", "Update Invoice Status in Sheet", and "Append Row to Recovery Log" nodes. Also update the Slack channel IDs and the legal team email address. Then activate the workflow. Use Cases and Variations Custom Escalation Rules — Modify the AI prompt or the "Needs Escalation?" condition to use different criteria (e.g., amount threshold, client tier). Multi-language Support — Adjust the AI prompt to generate emails in different languages. Integration with Accounting Software — Replace the Google Sheets nodes with API calls to QuickBooks, Xero, or other invoicing platforms. Different AI Models — Swap the GPT-4o-mini model for another OpenAI model or use a different provider via the LangChain integration. SMS or Other Channels — Add Twilio or other nodes to send reminders via SMS in addition to email. Dashboard & Reporting — Extend the logging sheet to feed into a BI tool for real-time recovery metrics.

19 nodes

AI Expense Tracker: Save Chat Messages to Google Sheets via n8n Chat

Overview This workflow turns your natural language expense entries into structured data and saves them to a Google Sheet. When you send a chat message like "car wash; 59.3 usd; 25 jan 2024" via the built-in n8n chat interface, the AI agent (powered by OpenAI) parses the text, extracts cost, description, and date, then triggers a sub-workflow that appends a new row in a Google Sheet. This is perfect for quick, conversational expense logging without manual data entry. Node Breakdown When chat message received (Chat Trigger) — No auth (n8n built-in webhook) — Listens for incoming chat messages from the n8n chat interface. No external credentials needed. AI Agent (@n8n/n8n-nodes-langchain.agent) — Uses the OpenAI Chat Model (API Key auth) and Window Buffer Memory (no auth). It receives the chat input, uses a system message instructing it to use the "saveexpensein_db" tool, and responds with a confirmation message. OpenAI Chat Model (OpenAI) — API Key auth — Connects to OpenAI's GPT model to process the chat message. Configured with default options. Window Buffer Memory (Memory) — No auth — Maintains conversation context across messages (buffer window). Parse msg and save to Sheets (Tool Workflow) — No auth (calls the same workflow as sub-workflow) — This is a tool that the AI agent can call. It passes the chat input to the sub-workflow's input trigger. The tool name is "saveexpensein_db". Workflow Input Trigger (Execute Workflow Trigger) — No auth — Receives the input from the tool call and passes it to the next node. Expense text to JSON parser (Information Extractor) — Uses OpenAI Chat Model1 (API Key auth) — Extracts structured data from the input text. It defines attributes: cost (number), desc (string), date (date). The prompt instructs to convert the expense text to JSON. OpenAI Chat Model1 (OpenAI) — API Key auth — Provides the language model for the information extractor. Save expense into Google Sheets (Google Sheets) — OAuth2 auth — Appends a new row to a Google Sheet. The sheet is specified by document ID and sheet name. Columns are mapped: msg (original input), cost, date, desc. Uses append operation. Sticky Note — No auth — Contains setup instructions for the user. Setup Instructions **Google Sheets: Create a copy of the expense template (File → Make a copy). Note the new spreadsheet ID. OpenAI API Key: Obtain an API key from OpenAI and add it as a credential in n8n for the OpenAI Chat Model nodes. Google Sheets Credential: Set up OAuth2 credential in n8n for Google Sheets with access to the spreadsheet. Sub-workflow reference: Open the "Parse msg and save to Sheets" node and ensure the workflow dropdown points to this same workflow (it should auto-select the current workflow). Activate the workflow to enable the chat trigger. Then send a message like "car wash; 59.3 usd; 25 jan 2024" to the chat interface. You'll receive a confirmation and a new row will appear in your Google Sheet. Use Cases & Variations Personal expense tracking: Log daily expenses on the go via chat. Team expense reports: Modify the Google Sheet to include employee names or categories. Multi-language support: The AI can parse expenses in different languages. Add receipt images: Extend the workflow with an image-to-text node to extract data from photos. Integrate with accounting software: Replace Google Sheets with a database or API call to QuickBooks, Xero, etc.

10 nodes

Extract,Save Invoice Data from Google Drive and send to Sheets using AI

This workflow automates the extraction of structured invoice data from PDF files placed in a Google Drive folder and saves the results into a Google Sheet. It uses Dumpling AI's document extraction API to parse key fields like order number, line items, totals, and addresses. This is ideal for accounting teams, procurement departments, or anyone handling a high volume of supplier invoices. Node Breakdown Google Drive Trigger – Watch Folder for New Files (Google Drive Trigger) — OAuth2. Polls a specified Google Drive folder every minute and triggers the workflow when a new file is created. Download Invoice File (Google Drive) — OAuth2. Downloads the newly created file from Drive using its file ID (). Convert invoice File to Base64 (Extract From File) — No auth. Converts the downloaded file binary to a base64 string for API submission. Send file to Dumpling AI for Data Extraction (HTTP Request) — Header Auth. Sends a POST request to with a JSON body containing the base64 file and a prompt instructing the AI to extract order number, document date, PO number, shipping/sold-to details, line items, and total amounts. The API key is passed via an HTTP header credential. Parse Dumpling AI JSON Response (Code) — No auth. Executes JavaScript to parse the field from the API response (a JSON string) into a structured object. Split line Items from Invoice (Split Out) — No auth. Splits the array into separate items so each line item becomes a row in the spreadsheet. Save Data to Google Sheet (Google Sheets) — OAuth2. Appends a row to the target sheet with all extracted fields mapped to columns (Order number, Document Date, PO number, Sold to/Ship to names and addresses, Model, Description, Quantity, Unit Price, Total Price). Setup Instructions Google Services: You need a Google account with access to Google Drive and Google Sheets. Create a Google Drive folder for incoming invoices and a Google Sheet (with headers matching the column names above). In n8n, use OAuth2 credentials for both the Google Drive and Google Sheets nodes. Dumpling AI: Sign up for a Dumpling AI account and obtain an API key. In n8n, create an HTTP Header Auth credential with the key name (e.g., ) and value (e.g., ). Workflow Configuration: Update the folder ID in the Google Drive Trigger node, the document ID and sheet name in the Google Sheets node, and ensure the Dumpling AI endpoint and prompt are correct. The prompt can be customized to extract different fields. Use Cases and Variations Accounts Payable Automation: Automatically capture invoice details and post them to accounting software like QuickBooks, Xero, or ERP systems. Multi-format Support: Dumpling AI can handle PDFs, images, and other document types. Modify the prompt to extract data from purchase orders, receipts, or contracts. Error Handling: Add an error workflow or a Slack notification if extraction fails, or route failed invoices to a separate review sheet. Batch Processing: Replace the trigger with a scheduled poll or manual upload event to process existing invoices retroactively.

8 nodes

Ready to automate with n8n?

Get affordable managed n8n hosting with 24/7 support.