Staff check-in and check-out with a Telegram bot

Staff send "in" or "out" (or دخول / خروج) to a Telegram bot, and every check-in lands in an attendance sheet.

Easy Saves ~4 hours/month HR & TeamBy Flowngn team

Works with n8n · Make · Zapier

How it works

Trigger: Telegram — a staff member messages the bot
  1. A staff member sends "in" or "out" to your bot.
  2. Read who sent it and the time.
  3. Add a row to the Attendance sheet.
  4. Reply with a confirmation and the time.

Diagram

TMessage to botTelegram Trigger{ }Read check-inCodeGLog attendanceGoogle SheetsTConfirmTelegram
Trigger App Code

Code

JavaScript used in the Code step — already included in the template below.

Read check-in · Code node
// Turns "in", "out", "دخول" or "خروج" sent to the bot into an attendance row.
const msg = $input.first().json.message;
const text = String(msg?.text || "").trim().toLowerCase();

const IN = ["in", "check in", "دخول", "حضور"];
const OUT = ["out", "check out", "خروج", "انصراف"];
const action = IN.includes(text) ? "in" : OUT.includes(text) ? "out" : null;
if (!action) return [];

const at = new Date(msg.date * 1000);
return [{
  json: {
    chatId: msg.chat.id,
    telegram_id: msg.from.id,
    name: [msg.from.first_name, msg.from.last_name].filter(Boolean).join(" "),
    action,
    date: at.toISOString().slice(0, 10),
    time: at.toISOString().slice(11, 16),
  },
}];

Build it

Pick your tool — same automation, step by step.

Copy and paste straight into an n8n canvas (Ctrl+V), or download and use Import from File. Then connect your own credentials and replace every YOUR_… placeholder.

Workflow JSON
Show code (144 lines)
{
  "name": "Staff check-in and check-out with a Telegram bot (Flowngn)",
  "nodes": [
    {
      "parameters": {
        "content": "## Staff check-in and check-out with a Telegram bot\nStaff send \"in\" or \"out\" (or دخول / خروج) to a Telegram bot, and every check-in lands in an attendance sheet.\n\n**Setup**\n1. Open each node and connect your own credentials.\n2. Replace every YOUR_… placeholder.\n3. Test once, then activate.\n\nFree template from Flowngn",
        "height": 260,
        "width": 340
      },
      "id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
      "name": "Setup notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -420,
        -60
      ]
    },
    {
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
      "name": "Message to bot",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.1,
      "position": [
        0,
        0
      ],
      "webhookId": "3da33d1f-b8e3-42d1-aace-213b83d434e0"
    },
    {
      "parameters": {
        "jsCode": "// Turns \"in\", \"out\", \"دخول\" or \"خروج\" sent to the bot into an attendance row.\nconst msg = $input.first().json.message;\nconst text = String(msg?.text || \"\").trim().toLowerCase();\n\nconst IN = [\"in\", \"check in\", \"دخول\", \"حضور\"];\nconst OUT = [\"out\", \"check out\", \"خروج\", \"انصراف\"];\nconst action = IN.includes(text) ? \"in\" : OUT.includes(text) ? \"out\" : null;\nif (!action) return [];\n\nconst at = new Date(msg.date * 1000);\nreturn [{\n  json: {\n    chatId: msg.chat.id,\n    telegram_id: msg.from.id,\n    name: [msg.from.first_name, msg.from.last_name].filter(Boolean).join(\" \"),\n    action,\n    date: at.toISOString().slice(0, 10),\n    time: at.toISOString().slice(11, 16),\n  },\n}];"
      },
      "id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
      "name": "Read check-in",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        300,
        0
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Attendance",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "date": "={{ $json.date }}",
            "time": "={{ $json.time }}",
            "name": "={{ $json.name }}",
            "telegram_id": "={{ $json.telegram_id }}",
            "action": "={{ $json.action }}"
          }
        },
        "options": {}
      },
      "id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
      "name": "Log attendance",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        600,
        0
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $('Read check-in').item.json.chatId }}",
        "text": "=✅ {{ $('Read check-in').item.json.action === 'in' ? 'Checked in' : 'Checked out' }} at {{ $('Read check-in').item.json.time }} UTC",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
      "name": "Confirm",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        900,
        0
      ]
    }
  ],
  "connections": {
    "Message to bot": {
      "main": [
        [
          {
            "node": "Read check-in",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read check-in": {
      "main": [
        [
          {
            "node": "Log attendance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log attendance": {
      "main": [
        [
          {
            "node": "Confirm",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}

Open in the playground to edit →

Tips

  • Times are saved in UTC; add your offset in the sheet or in the Code step.
  • Pin the bot in a staff group so nobody forgets it.

Related flows