AutomationFlowsFinance › Issue Gapless Sequential Invoice and Ticket Numbers From a Data Table

Issue Gapless Sequential Invoice and Ticket Numbers From a Data Table

Issue gapless sequential invoice and ticket numbers from a Data Table. Uses dataTable. Webhook trigger; 10 nodes.

Webhook trigger★★★★☆ complexity10 nodesData Table
Finance Trigger: Webhook Nodes: 10 Complexity: ★★★★☆ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Issue gapless sequential invoice and ticket numbers from a Data Table",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "next-number",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "2b10df80-14da-4f8b-9e2a-9e4f165a9d08",
      "name": "When Number Is Requested",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        576,
        288
      ]
    },
    {
      "parameters": {
        "jsCode": "var items = $input.all();\nvar src = items.length ? items[0].json : {};\nvar body = (src.body && typeof src.body === 'object') ? src.body : {};\nvar query = (src.query && typeof src.query === 'object') ? src.query : {};\nfunction pick(name, fallback){\n  if (body[name] !== undefined && body[name] !== null && body[name] !== '') return body[name];\n  if (query[name] !== undefined && query[name] !== null && query[name] !== '') return query[name];\n  if (src[name] !== undefined && src[name] !== null && src[name] !== '') return src[name];\n  return fallback;\n}\nvar keyRe = new RegExp('^[a-z0-9_-]{1,50}$');\nvar key = String(pick('sequence_key', 'invoice')).trim().toLowerCase();\nif (!keyRe.test(key)) key = 'invoice';\nvar prefixRe = new RegExp('^[A-Za-z0-9_-]{0,12}$');\nvar prefix = String(pick('prefix', '')).trim();\nif (!prefixRe.test(prefix)) prefix = '';\nvar pad = parseInt(pick('pad', 6), 10);\nif (!isFinite(pad) || pad < 1) pad = 6;\nif (pad > 12) pad = 12;\nreturn [{ json: { sequence_key: key, prefix: prefix, pad: pad, prefix_requested: prefix.length > 0 } }];"
      },
      "id": "df0841fe-e127-47a7-bf8b-086e7454a396",
      "name": "Read Sequence Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        816,
        288
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "number_sequences"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "sequence_key",
              "keyValue": "={{ $json.sequence_key }}"
            }
          ]
        },
        "limit": 1
      },
      "id": "61cc150e-7408-4a44-8134-64ac0f78ca77",
      "name": "Get Sequence Row",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        1104,
        288
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "var req = $('Read Sequence Request').first().json;\nvar items = $input.all();\nvar row = (items.length && items[0].json && typeof items[0].json === 'object') ? items[0].json : {};\nvar stored = Number(row.current_value);\nif (!isFinite(stored) || stored < 0) stored = 0;\nstored = Math.floor(stored);\nvar next = stored + 1;\nvar prefix = req.prefix_requested ? req.prefix : (typeof row.prefix === 'string' ? row.prefix : '');\nvar digits = String(next);\nwhile (digits.length < req.pad) { digits = '0' + digits; }\nreturn [{ json: {\n  sequence_key: req.sequence_key,\n  current_value: next,\n  previous_value: stored,\n  prefix: prefix,\n  formatted_number: prefix + digits,\n  updated_at: new Date().toISOString()\n} }];"
      },
      "id": "6722f35a-4773-4d6e-a748-c63200cc2adc",
      "name": "Compute Next Number",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1360,
        288
      ]
    },
    {
      "parameters": {
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "number_sequences"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "sequence_key",
              "keyValue": "={{ $json.sequence_key }}"
            }
          ]
        },
        "columns": {
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "sequence_key"
          ],
          "value": {
            "sequence_key": "={{ $json.sequence_key }}",
            "current_value": "={{ $json.current_value }}",
            "prefix": "={{ $json.prefix }}",
            "updated_at": "={{ $json.updated_at }}"
          },
          "schema": [
            {
              "id": "sequence_key",
              "displayName": "sequence_key",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "current_value",
              "displayName": "current_value",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "prefix",
              "displayName": "prefix",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "updated_at",
              "displayName": "updated_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "d84bfebd-73fb-4609-b676-47b50a6a701a",
      "name": "Save Sequence Counter",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        1664,
        288
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { \"sequence_key\": $(\"Compute Next Number\").item.json.sequence_key, \"number\": $(\"Compute Next Number\").item.json.formatted_number, \"current_value\": $(\"Compute Next Number\").item.json.current_value, \"issued_at\": $(\"Compute Next Number\").item.json.updated_at } }}",
        "options": {}
      },
      "id": "f47277b9-959d-4826-b215-e7b8cbfdcb27",
      "name": "Return Issued Number",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        1920,
        288
      ]
    },
    {
      "parameters": {
        "content": "## Issue gapless sequential invoice and ticket numbers from a Data Table\n\n### How it works\n1. A `POST` webhook at `next-number` accepts a `sequence_key`, plus an optional `prefix` and zero-pad width `pad`.\n2. A Code node validates the request and falls back to safe defaults, so a malformed call still returns a usable number.\n3. The `number_sequences` Data Table is read for that key, then a Code node adds one to `current_value`, starting at 1 when the key has never been used.\n4. The counter is written back with an upsert on `sequence_key`, and Respond to Webhook returns the formatted number, for example `INV-000042`.\n\n### Setup steps\n- [ ] Create a Data Table named `number_sequences` with the columns `sequence_key`, `current_value`, `prefix`, and `updated_at`.\n- [ ] Confirm that selection on both `Get Sequence Row` and `Save Sequence Counter`.\n- [ ] Activate the workflow, then POST `{\"sequence_key\": \"invoice\"}` to the production URL.\n- [ ] Set this workflow to a single concurrent execution before you rely on the sequence being unbroken.\n\n### Customization\nData Tables give no atomic read-modify-write, so the no-gaps promise is best effort: two genuinely simultaneous calls can read the same `current_value`. Serialise your callers if you need a hard guarantee. Change the default pad width in `Compute Next Number`, or turn on header auth on the webhook to keep the issuer private.",
        "height": 724,
        "width": 692
      },
      "id": "8d6cf6d9-8d5a-4ed9-ad5a-7b7496c271fe",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -256,
        -80
      ]
    },
    {
      "parameters": {
        "content": "## Read and validate the request",
        "height": 312,
        "width": 480,
        "color": 7
      },
      "id": "034288b5-f2fe-413a-b89f-bb0fa8e2d891",
      "name": "Section Read Request",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        512,
        176
      ]
    },
    {
      "parameters": {
        "content": "## Look up and increment\n\nBest effort, not a lock: two genuinely simultaneous calls can read the same `current_value`. Serialise callers for a hard guarantee.",
        "height": 376,
        "width": 496,
        "color": 7
      },
      "id": "979df71f-c2d1-424a-9d7c-882518bf63ee",
      "name": "Section Look Up Increment",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1040,
        96
      ]
    },
    {
      "parameters": {
        "content": "## Save the counter and respond",
        "height": 376,
        "width": 544,
        "color": 7
      },
      "id": "fe2bb9c7-aa5b-409e-bfe8-1968b2c72255",
      "name": "Section Save And Respond",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1584,
        96
      ]
    }
  ],
  "connections": {
    "When Number Is Requested": {
      "main": [
        [
          {
            "node": "Read Sequence Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Sequence Request": {
      "main": [
        [
          {
            "node": "Get Sequence Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Sequence Row": {
      "main": [
        [
          {
            "node": "Compute Next Number",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute Next Number": {
      "main": [
        [
          {
            "node": "Save Sequence Counter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Sequence Counter": {
      "main": [
        [
          {
            "node": "Return Issued Number",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": []
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Issue gapless sequential invoice and ticket numbers from a Data Table. Uses dataTable. Webhook trigger; 10 nodes.

Source: https://github.com/exekyute/n8n-exekyute-templates/blob/main/published/n8n-gapless-number-issuer/workflow.json — original creator credit. Request a take-down →

More Finance workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Finance

AP Invoice — 01 Orchestrator. Uses postgres, httpRequest, quickbooks, slack. Webhook trigger; 42 nodes.

Postgres, HTTP Request, QuickBooks +1
Finance

This workflow automates the entire process of receiving a product/service order, checking or creating a customer in QuickBooks Online (QBO), generating an invoice, and emailing it — all triggered by a

QuickBooks
Finance

Tired of the standard, boring invoices from QuickBooks Online? This workflow completely automates the process of creating beautiful, custom-branded PDF invoices and emailing them directly to your clie

QuickBooks, HTTP Request, Email Send
Finance

Automated QuickBooks Invoice to Custom PDF & Email

QuickBooks, HTTP Request, Email Send
Finance

This workflow automates the full invoicing and payment process using n8n and Xero. It allows businesses to generate invoices, track payments, send WhatsApp notifications, and keep records synced autom

Xero, Google Calendar, Twilio +1