台帳堂Daichodo

変更検知 Webhook

監視したい番号を登録しておくと、国税庁の差分データにその番号の変更が現れた時点で HTTPS POST をお送りします。

照会は国税庁が無償で提供しており、1件ずつであれば変更履歴も取得できます。ただし向こうから知らせてはくれません。監視したいすべての番号を毎日照会し続ける代わりになるのが変更検知です。

1. 送信先を登録する

ダッシュボード の「変更検知」から URL を登録します。登録時に署名シークレットが一度だけ表示されます。再表示できません。

URL には制限があります。

  • https のみ。http は受け付けません。署名ヘッダーも本文も経路上で読み取れてしまうためです。
  • ループバック、プライベート、リンクローカルのアドレスは受け付けません。

2. 監視する番号を登録する

登録番号(T + 13桁)または法人番号(13桁)を登録します。ハイフンや全角ダッシュを含めて貼り付けても構いません。サーバー側で登録簿と同じ表記に正規化します。

変更検知は Standard プラン以上でご利用いただけます。

3. 受信する

{
  "id": 918273,
  "type": "registry.change",
  "registry": "invoice",
  "subject_key": "T8000000000001",
  "process": "02",
  "correct": null,
  "effective_on": "2026-08-11",
  "observed_at": "2026-08-12T02:14:33+00:00",
  "record": { "name": "株式会社サンプル", "...": "..." }
}

process は国税庁の事業者処理区分をそのまま渡しています。独自の語彙に置き換えていません。

コード意味
01新規登録
02変更
03失効
04取消

公表されている仕様にないコードが実データに現れることがあります。その場合も行を破棄せず、生の値のままお送りします。解釈できないコードであっても、後から取得し直せないデータだからです。

4. 署名を検証する

すべてのリクエストに Daichodo-Signature ヘッダーが付きます。

Daichodo-Signature: t=1786400000,v1=<hex hmac-sha256 of "{t}.{生の本文}">

Stripe と同じ構成を意図的に採用しています。すでに Stripe の検証を実装されている場合、ヘッダー名を変えるだけで移植できます。

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=', 2)));
  // タイムスタンプは署名対象に含まれています。t だけを書き換えても署名は一致しません。
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;

  const expected = createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');

  // 長さの違いで timingSafeEqual が例外を投げるため、先に確認します。
  if (expected.length !== parts.v1.length) return false;
  return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(
        secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

必ず生のリクエストボディに対して検証してください。 JSON をパースして再度文字列化するとバイト列が変わり、署名が一致しなくなります。

再送について

応答動作
2xx成功
408、429再送します
その他の 4xx再送しません
3xx、5xx、接続失敗、タイムアウト再送します

408 と 429 以外の 4xx は「このリクエストは不正だ」という応答であり、次回も同じ結果になります。再送は復旧にならず、負荷になるだけです。

再送は最大 8 回、間隔を空けて行います。リダイレクトは追跡しません。

送信の記録はダッシュボードの「送信履歴」で確認できます。送信前に記録を作成しているため、一度も届かなかった通知も残ります。

冪等に実装してください

同じ通知が二度届くことがあります。id は登録簿イベントの識別子なので、受信側で id による重複排除を行ってください。