LINEの打刻をスプレッドシートに
自動記録する手順
スタッフが「出勤」「退勤」と送るだけ。日時が自動で表に貯まり、月末には集計まで届く仕組みを、コピペで作ります。
1. つくる仕組みの全体像と、最初の大事な注意
できあがる流れはとてもシンプルです。
- スタッフがLINEで「出勤」または「退勤」と送る(リッチメニューのボタンでもOK)
- GAS(Google の無料プログラム)がそれを受け取り、いま何時何分かをスプレッドシートに1行追記する
- スタッフには「〇〇さん、8:59 に『出勤』を記録しました」と自動で返信が届く
- 月初に、前月分の勤務日数・勤務時間が自動集計され、社長のLINEに届く
最初に決めておく大事なこと:打刻は「専用のLINE公式アカウント」で。
この打刻は、LINEからのメッセージをGASが受信して動きます。LINEの受信先(Webhook)は1アカウントにつき1つしか設定できないため、すでにエルメ(Lmessage)で集客・自動応答を動かしているアカウントにこの仕組みを入れると、エルメ側が止まります。
そこで本記事では、打刻専用のLINE公式アカウントを新しく1つ用意し、そこにこの仕組みを載せる前提で進めます。集客用のエルメ本体アカウントとは分けてください。
2. 準備するもの(打刻用のLINEとアクセストークン)
用意するのは次の3つです。すべて無料で始められます。
- 打刻専用のLINE公式アカウント(LINE公式アカウントマネージャーで新規作成)
- Messaging APIの利用設定(このアカウントをプログラムから動かすための入口)
- Googleアカウント(スプレッドシートとGASを使うため。普段のGmailでOK)
Messaging APIを有効にしてトークンを取る
「チャネルアクセストークン」は、GASがLINEに返信するための鍵です。次の手順で発行します。
操作場所:LINE Developers > プロバイダー作成 > Messaging APIチャネル作成
- LINE Developersコンソールにログインし、「プロバイダー」を1つ作る(会社名でOK)
- その中に「Messaging API」チャネルを作る(打刻用アカウントとひも付け)
- チャネルの「Messaging API設定」タブを開く
- いちばん下の「チャネルアクセストークン(長期)」を発行し、表示された長い文字列をコピーしておく(後でGASに貼る)
- 同じ画面の「応答設定」で、Webhook:オン/応答メッセージ:オフにする
「応答メッセージ」は必ずオフ。ここがオンのままだと、LINE標準の自動あいさつと、これから作るGASの返信が二重に飛びます。
3. 記録先のスプレッドシートを用意する
Googleドライブで新しいスプレッドシートを1つ作り、名前を「勤怠管理」などにします。この中に、シートを2枚用意します。
シート①「打刻ログ」(1回の打刻ごとに1行)
1行目に、次の見出しをA列から順番どおりに入れてください。この並びはGASと完全に一致させる必要があります。
| 列 | 見出し | 入るもの | 備考 |
|---|---|---|---|
| A | 記録日時 | 2026/04/01 8:59:12 | GASが自動で入れる |
| B | 日付 | 2026/04/01 | 集計のキー |
| C | 時刻 | 08:59:12 | 集計のキー |
| D | 社員名 | 山田 太郎 | LINEの表示名 |
| E | ユーザーID | Uxxxxxxxx… | LINEが割り振る固有ID |
| F | 種別 | 出勤/退勤 | 送られた言葉 |
シート②「月次集計」(集計結果が貯まる)
もう1枚シートを追加し、名前を「月次集計」に。1行目の見出しは次の4つです。
| 列 | 見出し | 入るもの |
|---|---|---|
| A | 年月 | 2026/04 |
| B | 社員名 | 山田 太郎 |
| C | 出勤日数 | 20 |
| D | 総勤務時間 | 160時間30分 |
スプレッドシートのIDを控える
ブラウザのアドレスバーに出ている、次の太字部分(長い英数字)が「スプレッドシートID」です。あとでGASに貼ります。
4. 打刻を受け取るGASを貼り付ける
先ほどのスプレッドシートを開き、上部メニューから GAS のエディタを開きます。
操作場所:スプレッドシート > 拡張機能 > Apps Script
- Apps Scriptのエディタが開いたら、最初から書かれている
function myFunction(){}をすべて消す - 下のコードをまるごとコピーして貼り付ける
- 先頭の3か所(トークン・スプレッドシートID・管理者ID)を、自分のものに書き換える
- フロッピーマークで保存する
コピペする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 '(名前取得失敗)';
}
}
※ この時点ではまだLINEと接続していないので返信は来ません。接続は次の章です。
5. 公開してLINEとつなぐ(Webhook・リッチメニュー)
① GASを「ウェブアプリ」として公開する
GASを外から呼び出せるURLに変えます。
操作場所:Apps Script > デプロイ > 新しいデプロイ
- 右上の「デプロイ」→「新しいデプロイ」を選ぶ
- 種類の歯車から「ウェブアプリ」を選ぶ
- 「次のユーザーとして実行:自分」/「アクセスできるユーザー:全員」に設定する
- 「デプロイ」を押し、初回は権限の許可を求められるので承認する
- 表示されたウェブアプリのURLをコピーする
② そのURLをLINEのWebhookに登録する
操作場所:LINE Developers > Messaging API設定 > Webhook URL
- 「Webhook URL」に、先ほどコピーしたウェブアプリのURLを貼る
- 「検証」を押して成功と出れば接続完了
- 「Webhookの利用」がオンになっていることを確認する
ここまでで、打刻用アカウントに「出勤」と送ると、シートに記録され、返信が返ってくるようになります。まず自分のスマホで友だち追加してテストしてください。
③ リッチメニューに「出勤/退勤」ボタンを付ける(任意)
スタッフが毎回手で「出勤」と打つのは面倒なので、タップするだけで送れるボタンを用意します。
操作場所:LINE公式アカウントマネージャー > ホーム > リッチメニュー
- リッチメニューを新規作成し、2分割などのレイアウトを選ぶ
- 左のボタンのアクションを「テキスト」にして、送信内容に 出勤 と入れる
- 右のボタンも同様に、送信内容に 退勤 と入れる
- 保存して表示をオンにする
ボタンの送信内容は、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 > 左メニューの時計マーク(トリガー)> トリガーを追加
- 実行する関数を aggregateMonthly にする
- イベントのソースを「時間主導型」→「月ベースのタイマー」にする
- 日を「1日」、時刻を「午前6〜7時」あたりに設定して保存する
・山田 太郎:20日 / 160h30m
・佐藤 花子:18日 / 142h0m
※ この集計は「その日の最初の出勤〜最後の退勤」を1日の勤務時間とみなす簡易版です。休憩時間の控除や打刻忘れの扱いは、運用に合わせて調整してください。
まとめ
スタッフがLINEで「出勤/退勤」と送るだけで、日時がスプレッドシートに自動で貯まり、月初に勤務時間まで集計されて社長に届く——これだけの仕組みが、無料のGASとスプレッドシートで作れます。
まず最初の1手は、打刻専用のLINE公式アカウントを1つ作ること。集客用のエルメ本体とは分けて用意すれば、あとは章2→章6の順にコピペで進むだけです。タイムカードの回収も、Excelへの手入力も、これで不要になります。







