LINEの打刻をスプレッドシートに自動記録する手順

LINE公式アカウント × GAS × スプレッドシート

LINEの打刻をスプレッドシートに
自動記録する手順

スタッフが「出勤」「退勤」と送るだけ。日時が自動で表に貯まり、月末には集計まで届く仕組みを、コピペで作ります。

1. つくる仕組みの全体像と、最初の大事な注意

できあがる流れはとてもシンプルです。

  1. スタッフがLINEで「出勤」または「退勤」と送る(リッチメニューのボタンでもOK)
  2. GAS(Google の無料プログラム)がそれを受け取り、いま何時何分かをスプレッドシートに1行追記する
  3. スタッフには「〇〇さん、8:59 に『出勤』を記録しました」と自動で返信が届く
  4. 月初に、前月分の勤務日数・勤務時間が自動集計され、社長のLINEに届く

最初に決めておく大事なこと:打刻は「専用のLINE公式アカウント」で。

この打刻は、LINEからのメッセージをGASが受信して動きます。LINEの受信先(Webhook)は1アカウントにつき1つしか設定できないため、すでにエルメ(Lmessage)で集客・自動応答を動かしているアカウントにこの仕組みを入れると、エルメ側が止まります。
そこで本記事では、打刻専用のLINE公式アカウントを新しく1つ用意し、そこにこの仕組みを載せる前提で進めます。集客用のエルメ本体アカウントとは分けてください。

◆ ◆ ◆

2. 準備するもの(打刻用のLINEとアクセストークン)

用意するのは次の3つです。すべて無料で始められます。

  1. 打刻専用のLINE公式アカウント(LINE公式アカウントマネージャーで新規作成)
  2. Messaging APIの利用設定(このアカウントをプログラムから動かすための入口)
  3. Googleアカウント(スプレッドシートとGASを使うため。普段のGmailでOK)

Messaging APIを有効にしてトークンを取る

「チャネルアクセストークン」は、GASがLINEに返信するための鍵です。次の手順で発行します。

操作場所:LINE Developers > プロバイダー作成 > Messaging APIチャネル作成

  1. LINE Developersコンソールにログインし、「プロバイダー」を1つ作る(会社名でOK)
  2. その中に「Messaging API」チャネルを作る(打刻用アカウントとひも付け)
  3. チャネルの「Messaging API設定」タブを開く
  4. いちばん下の「チャネルアクセストークン(長期)」を発行し、表示された長い文字列をコピーしておく(後でGASに貼る)
  5. 同じ画面の「応答設定」で、Webhook:オン/応答メッセージ:オフにする

「応答メッセージ」は必ずオフ。ここがオンのままだと、LINE標準の自動あいさつと、これから作るGASの返信が二重に飛びます。

◆ ◆ ◆

3. 記録先のスプレッドシートを用意する

Googleドライブで新しいスプレッドシートを1つ作り、名前を「勤怠管理」などにします。この中に、シートを2枚用意します。

シート①「打刻ログ」(1回の打刻ごとに1行)

1行目に、次の見出しをA列から順番どおりに入れてください。この並びはGASと完全に一致させる必要があります。

列見出し入るもの備考
A記録日時2026/04/01 8:59:12GASが自動で入れる
B日付2026/04/01集計のキー
C時刻08:59:12集計のキー
D社員名山田 太郎LINEの表示名
EユーザーIDUxxxxxxxx…LINEが割り振る固有ID
F種別出勤/退勤送られた言葉

シート②「月次集計」(集計結果が貯まる)

もう1枚シートを追加し、名前を「月次集計」に。1行目の見出しは次の4つです。

列見出し入るもの
A年月2026/04
B社員名山田 太郎
C出勤日数20
D総勤務時間160時間30分

スプレッドシートのIDを控える

ブラウザのアドレスバーに出ている、次の太字部分(長い英数字)が「スプレッドシートID」です。あとでGASに貼ります。

https://docs.google.com/spreadsheets/d/1AbCdEf…ここがID…XyZ/edit
◆ ◆ ◆

4. 打刻を受け取るGASを貼り付ける

先ほどのスプレッドシートを開き、上部メニューから GAS のエディタを開きます。

操作場所:スプレッドシート > 拡張機能 > Apps Script

  1. Apps Scriptのエディタが開いたら、最初から書かれている function myFunction(){} をすべて消す
  2. 下のコードをまるごとコピーして貼り付ける
  3. 先頭の3か所(トークン・スプレッドシートID・管理者ID)を、自分のものに書き換える
  4. フロッピーマークで保存する

コピペするGAS(このまま貼ってOK)

// ▼ ここだけ自分のものに書き換える 3か所 ------------------
const TOKEN     = 'ここにチャネルアクセストークンを貼る';   // 章2で取得
const SHEET_ID  = 'ここにスプレッドシートIDを貼る';         // 章3で控えたID
const ADMIN_ID  = 'ここに社長のユーザーIDを貼る';           // 章6で取得(今は空でOK)
// --------------------------------------------------------
const LOG_SHEET = '打刻ログ';
const SUM_SHEET = '月次集計';
const TZ = 'Asia/Tokyo';

// LINEから打刻メッセージを受け取り、シートに1行追記して返信する
function doPost(e) {
  const ev = JSON.parse(e.postData.contents).events[0];
  if (!ev || ev.type !== 'message' || ev.message.type !== 'text') {
    return ContentService.createTextOutput('OK');
  }
  const text = ev.message.text.trim();          // 「出勤」か「退勤」を想定
  if (text !== '出勤' && text !== '退勤') {
    reply(ev.replyToken, '「出勤」または「退勤」と送ってください。');
    return ContentService.createTextOutput('OK');
  }

  const userId = ev.source.userId;
  const name   = getDisplayName(userId);
  const now    = new Date();
  const dateStr = Utilities.formatDate(now, TZ, 'yyyy/MM/dd');
  const timeStr = Utilities.formatDate(now, TZ, 'HH:mm:ss');

  // A:記録日時 B:日付 C:時刻 D:社員名 E:ユーザーID F:種別 の順で1行追記
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(LOG_SHEET);
  sheet.appendRow([now, dateStr, timeStr, name, userId, text]);

  reply(ev.replyToken, name + 'さん、' + timeStr + ' に「' + text + '」を記録しました。');
  return ContentService.createTextOutput('OK');
}

// 返信(受信の応答なので reply。無料メッセージ数を消費しない)
function reply(token, message) {
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    method: 'post',
    contentType: 'application/json',
    headers: { Authorization: 'Bearer ' + TOKEN },
    payload: JSON.stringify({ replyToken: token, messages: [{ type: 'text', text: message }] })
  });
}

// ユーザーIDから、LINEの表示名を取得する
function getDisplayName(userId) {
  try {
    const res = UrlFetchApp.fetch('https://api.line.me/v2/bot/profile/' + userId, {
      headers: { Authorization: 'Bearer ' + TOKEN }
    });
    return JSON.parse(res.getContentText()).displayName;
  } catch (err) {
    return '(名前取得失敗)';
  }
}
出勤
山田 太郎さん、8:59:12 に「出勤」を記録しました。

※ この時点ではまだLINEと接続していないので返信は来ません。接続は次の章です。

◆ ◆ ◆

5. 公開してLINEとつなぐ(Webhook・リッチメニュー)

① GASを「ウェブアプリ」として公開する

GASを外から呼び出せるURLに変えます。

操作場所:Apps Script > デプロイ > 新しいデプロイ

  1. 右上の「デプロイ」→「新しいデプロイ」を選ぶ
  2. 種類の歯車から「ウェブアプリ」を選ぶ
  3. 「次のユーザーとして実行:自分」/「アクセスできるユーザー:全員」に設定する
  4. 「デプロイ」を押し、初回は権限の許可を求められるので承認する
  5. 表示されたウェブアプリのURLをコピーする

② そのURLをLINEのWebhookに登録する

操作場所:LINE Developers > Messaging API設定 > Webhook URL

  1. 「Webhook URL」に、先ほどコピーしたウェブアプリのURLを貼る
  2. 「検証」を押して成功と出れば接続完了
  3. 「Webhookの利用」がオンになっていることを確認する

ここまでで、打刻用アカウントに「出勤」と送ると、シートに記録され、返信が返ってくるようになります。まず自分のスマホで友だち追加してテストしてください。

③ リッチメニューに「出勤/退勤」ボタンを付ける(任意)

スタッフが毎回手で「出勤」と打つのは面倒なので、タップするだけで送れるボタンを用意します。

操作場所:LINE公式アカウントマネージャー > ホーム > リッチメニュー

  1. リッチメニューを新規作成し、2分割などのレイアウトを選ぶ
  2. 左のボタンのアクションを「テキスト」にして、送信内容に 出勤 と入れる
  3. 右のボタンも同様に、送信内容に 退勤 と入れる
  4. 保存して表示をオンにする

ボタンの送信内容は、GASが判定する言葉と1文字も違わないようにしてください(「出勤する」ではなく「出勤」)。ズレると記録されません。

◆ ◆ ◆

6. 月末に勤務時間を自動集計する

打刻ログが貯まったら、月初に「前月は誰が何日・何時間働いたか」を自動でまとめ、社長のLINEに届けます。これは受信をしない(時刻で自動起動して送るだけの)処理なので、打刻の受信とぶつからずに同居できます。

① 社長のユーザーIDを取得してGASに入れる

集計結果の送り先を指定します。社長のスマホで打刻用アカウントを友だち追加し、一度「出勤」と送ると、章3の「打刻ログ」シートのE列に社長のユーザーIDが記録されます。その値をコピーし、章4のコード先頭 ADMIN_ID に貼ってください。

② 集計用のコードを追記する

章4のコードのいちばん下に、続けて次を貼り付けて保存します。

// 毎月1日に前月分を集計 → 「月次集計」シートに書き出し → 社長にpush
function aggregateMonthly() {
  const ss = SpreadsheetApp.openById(SHEET_ID);
  const rows = ss.getSheetByName(LOG_SHEET).getDataRange().getValues();
  rows.shift();  // 見出し行を除く

  // 打刻ログの列位置(A=0 から数える)… B:日付 C:時刻 D:社員名 F:種別
  const COL = { 日付: 1, 時刻: 2, 社員名: 3, 種別: 5 };

  // 集計する対象=先月(yyyy/MM)
  const d = new Date(); d.setDate(1); d.setMonth(d.getMonth() - 1);
  const ym = Utilities.formatDate(d, TZ, 'yyyy/MM');

  // 社員×日ごとに「出勤の最も早い時刻」「退勤の最も遅い時刻」を集める
  const daily = {};
  rows.forEach(function (r) {
    const day = Utilities.formatDate(new Date(r[COL.日付]), TZ, 'yyyy/MM/dd');
    if (day.slice(0, 7) !== ym) return;            // 先月以外は無視
    const key = r[COL.社員名] + '|' + day;
    daily[key] = daily[key] || { 名: r[COL.社員名], 出勤: null, 退勤: null };
    const t = r[COL.時刻];
    if (r[COL.種別] === '出勤') {
      if (!daily[key].出勤 || t < daily[key].出勤) daily[key].出勤 = t;
    } else if (r[COL.種別] === '退勤') {
      if (!daily[key].退勤 || t > daily[key].退勤) daily[key].退勤 = t;
    }
  });

  // 社員ごとに 出勤日数 と 総勤務時間(分)を合算
  const sum = {};
  Object.keys(daily).forEach(function (k) {
    const rec = daily[k];
    sum[rec.名] = sum[rec.名] || { 日数: 0, 分: 0 };
    if (rec.出勤 && rec.退勤) {
      sum[rec.名].日数 += 1;
      sum[rec.名].分  += toMinutes(rec.退勤) - toMinutes(rec.出勤);
    }
  });

  // 「月次集計」シートへ書き出し、社長へのメッセージも作る
  const out = ss.getSheetByName(SUM_SHEET);
  let msg = ym + ' の勤務集計\n';
  Object.keys(sum).forEach(function (name) {
    const h = Math.floor(sum[name].分 / 60);
    const m = sum[name].分 % 60;
    out.appendRow([ym, name, sum[name].日数, h + '時間' + m + '分']);
    msg += '・' + name + ':' + sum[name].日数 + '日 / ' + h + 'h' + m + 'm\n';
  });

  pushToAdmin(msg);
}

// 「HH:mm:ss」を分に直す
function toMinutes(hhmmss) {
  const p = String(hhmmss).split(':');
  return Number(p[0]) * 60 + Number(p[1]);
}

// 社長にpush(定期処理なので reply ではなく push)
function pushToAdmin(message) {
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/push', {
    method: 'post',
    contentType: 'application/json',
    headers: { Authorization: 'Bearer ' + TOKEN },
    payload: JSON.stringify({ to: ADMIN_ID, messages: [{ type: 'text', text: message }] })
  });
}

③ 毎月1日に自動実行する予約を入れる

操作場所:Apps Script > 左メニューの時計マーク(トリガー)> トリガーを追加

  1. 実行する関数を aggregateMonthly にする
  2. イベントのソースを「時間主導型」→「月ベースのタイマー」にする
  3. 日を「1日」、時刻を「午前6〜7時」あたりに設定して保存する
2026/04 の勤務集計
・山田 太郎:20日 / 160h30m
・佐藤 花子:18日 / 142h0m

※ この集計は「その日の最初の出勤〜最後の退勤」を1日の勤務時間とみなす簡易版です。休憩時間の控除や打刻忘れの扱いは、運用に合わせて調整してください。

まとめ

スタッフがLINEで「出勤/退勤」と送るだけで、日時がスプレッドシートに自動で貯まり、月初に勤務時間まで集計されて社長に届く——これだけの仕組みが、無料のGASとスプレッドシートで作れます。

まず最初の1手は、打刻専用のLINE公式アカウントを1つ作ること。集客用のエルメ本体とは分けて用意すれば、あとは章2→章6の順にコピペで進むだけです。タイムカードの回収も、Excelへの手入力も、これで不要になります。

※ 本記事は架空の小さな会社を想定したサンプルの構築例です。掲載コードはサンプルであり、ご利用は自己責任でお願いします。