Amazon11/12まで!スマイルSale開催中!セール会場へ

スマートリングPebble Index 01でGrok Botを動かす|Webhook連携と415エラーの解決法

スマートリング「Pebble Index 01」で話した内容は、Webhookを使ってGrok Botのルーティンに送れます。

ただし、PebbleアプリにGrok BotのWebhookのURLをそのまま入れると、テスト送信が「415 ERROR」で失敗します。

原因は送る形式の違いで、Pebbleはフォーム形式(multipart/form-data)でしか送らず、Grok BotのWebhookはJSONの本文を受け取るからです。

間にCloudflare Workerを挟んでJSONに直したところ、私の環境ではテスト送信で200 OK(1083ミリ秒)が返りました。

翌日の本番では、リングで話した依頼がYouTube担当のBotまで届いています。

この記事では、私(須澤)が自分のリングとGrok Botをつないだ手順と、415でつまずいた原因、使ったWorkerのコードをまとめました。

公式の資料は、2026年11月4日時点の内容です。

目次

できることと全体の流れ

リングのボタンを押しながら話すと、その内容が担当のBotに振り分けられ、結果がチャットに返ってきます。

Pebble Index 01からPebbleアプリ、Cloudflare Worker、Grok Botのルーティン、担当のBotへ届く流れと、直接つなぐと415になることを示す図
Pebble Index 01からGrok Botまでの流れ(筆者作成)
  1. リングのボタンを押しながら話す(Hold & Talk)
  2. Pebbleアプリが文字起こしして、Webhookで送る
  3. Cloudflare Workerが、フォーム形式をJSONに直して転送する
  4. Grok BotのWebhookのルーティンが受け取る
  5. 内容に合った担当のBot(WEB制作・AI BLOG・blog・ASPの管理・ポイ活・YouTube・LINEスタンプなど)に振り分ける
  6. 担当のBotが作業して、結果をチャットで報告する

たとえば歩きながら「ブログのネタ、〇〇について書きたい」とつぶやけば、blog担当のBotにメモが届きます。

録音されるのは、リングのボタンを押している間だけです。

録音はBluetoothでスマホのPebbleアプリに送られ、文字起こしやWebhookの送信はアプリ側で行われます。

スポンサーリンク

必要なもの

もの条件・料金メモ
Pebble Index 01スマホにPebbleアプリが要るWebhookの設定はアプリの「Index 01」の設定にある
Grok BotWebhookで起動するルーティンを作れることルーティンが動くたびに使用量を使う
Cloudflareのアカウント無料プランで足りるWorkersの無料プランは1日10万リクエストまで
Pebble公式ドキュメント・Grok Botヘルプ・Cloudflareの資料より(2026年11月4日確認)

Grok Botを使えるプランは、Grok Botのメールアドレスの記事の料金プランの節にまとめています。

手順1:Grok BotでWebhookのルーティンを作る

最初に作るのは、Grok BotのWebhookで起動するルーティンです。

チャットで「Pebbleから届いた内容を振り分けるルーティンを作って」と頼めば、Botが作ってくれます。

メールでルーティンを起動するやり方は、Grok Botのメールアドレスの作り方で説明しました。

できたルーティンをPCで開くと、Webhookの欄に次の3つが出ます。

欄中身使い道
POST toWebhookのURL送り先。末尾がルーティンごとのIDになっている
key秘密の鍵(crsr_で始まる)送る側が Authorization: Bearer に入れる
headerAuthorization: Bearer + 鍵の1行そのまま写せる形
Grok Botヘルプ Routines より(2026年11月4日確認)

URLの形は https://api2.cursor.sh/automations/webhook/<ID> でした。

ヘルプには、このURLにJSONの本文を送れば、Botがルーティンの指示と一緒にその本文を受け取ると書かれています。

200が返ったら、Grok Botが受け付けて実行を始めたという意味です。

指示を終えたという意味ではないので、結果はBotのチャットで確かめます。

鍵はパスワードと同じ扱いです。

チャットに貼ったり、スクリーンショットに写したりしないでください。

画面を人に見せるときは、URLの欄と鍵の欄をぼかしてください。

手順2:PebbleアプリのWebhookの設定を開く

Pebbleアプリで「Index 01」の設定を開き、「Webhook」をタップします。

Webhookは押し方ごとに設定が分かれていて、「Hold & Talk」と「Double click & hold」の2つがあります。

今回は「Hold & Talk」に設定しました。

PebbleアプリのWebhook・Hold & Talkの設定画面。Webhook URL、送る内容(Transcriptionを選択)、Authorizationヘッダーの欄
PebbleアプリのWebhookの設定画面(2026/11/04時点の表示。URLと鍵はぼかしています)
項目中身
Webhook URL送り先のURL
What to sendRecording(録音)・Transcription(文字起こし)・Both(両方)。今回はTranscription
Headers送るときに付けるヘッダー。名前と値の組をいくつでも足せる
Sign requestsHMAC-SHA256の署名を付けるかどうか
Send test eventテスト送信のボタン
Recent runs送った履歴。押し方ごとに直近20件
Pebble公式ドキュメントと筆者の画面より(2026年11月4日確認)

ボタンの押し方に何を割り当てるかは、Index 01の設定の「Ring Button」で決めます。

Hold & Talkに選べるのは、Index agent・Web search・MCP sandbox・Webhook only・Nothingの5つです。

Grok Botにだけ送るなら、選ぶのは「Webhook only」です。

Index agentなどを選んだままでも、「Also send to webhook」のスイッチを入れれば、Webhookにも写しが送られます。

Double click & holdにも別のWebhookを設定できるので、2つのルーティンを押し方で使い分けることもできます。

つまずいたところ:415 ERROR

最初は、Grok BotのURLをそのままPebbleに入れて、Authorizationのヘッダーを付けてテストしました。

結果は 415 ERROR でした。

送る形式の違いを疑って Content-Type: application/json のヘッダーを足しても、やはり415のままでした。

上の画面のRecent runsに残っている2回の415が、このときのものです。

原因:Pebbleはフォーム形式でしか送らない

Pebbleの公式ドキュメントを読むと、理由がわかりました。

PebbleのWebhookは multipart/form-data でPOSTする決まりで、変えられません。

ヘッダーでContent-Typeを書いても、本文はフォーム形式のままです。

415(Unsupported Media Type)は、受け取る側が本文の形式を受け付けないときのエラーです。

フォーム形式で送るPebbleと、JSONの本文を受け取るGrok Botの間で、形式が合っていなかったのが415の正体でした。

Pebbleから届く項目とヘッダー

フォームに入って届く項目は、次のとおりです。

項目中身届くとき
transcription文字起こしの文章TranscriptionかBothのとき
audio録音(m4a・モノラル・16kHz)RecordingかBothのとき
recordedAt録音した時刻(ミリ秒)いつも
clientいつも ringいつも
testtrueテスト送信のときだけ
Pebble公式ドキュメント Webhooks より(2026年11月4日確認)

ヘッダーには、自分で足したもののほかに、次のものが自動で付きます。

ヘッダー中身
X-Index-Trigger押し方(single-click-hold・double-click-hold・テストは test-event)
X-Index-Webhook-Version仕様の版。今は 1
X-Index-Delivery録音ごとのID。同じ録音を送り直しても同じなので、重複の判定に使える
X-Index-Testテスト送信のときだけ true
X-Index-Signature・X-Index-Timestamp署名を有効にしたときだけ付く
X-Audio-Size録音を送るときだけ。録音の大きさ
Pebble公式ドキュメント Webhooks より(2026年11月4日確認)

GitHubのZhigre/pebble-index-bridgeでも同じ415が取り上げられていて、間に中継役を挟んで解決しています。

解決法:Cloudflare Workerで形式を直す

そこで、PebbleとGrok Botの間にCloudflare Workerを挟み、フォーム形式をJSONに直して転送することにしました。

Workerは、Cloudflareの上で小さなプログラムを動かせる仕組みで、自分でサーバーを用意しなくて済みます。

設計で気をつけたのは、次の3点です。

  • Workerに秘密の値を持たせない:Pebbleが付けてくるAuthorizationのヘッダーを、そのまま転送するだけにした
  • URLのパスはそのまま使う:Pebbleには https://<Worker名>.<サブドメイン>.workers.dev/automations/webhook/<ID> を設定し、Workerは https://api2.cursor.sh に同じパスを付けて転送する
  • 転送先を固定する:受け付けるのは /automations/webhook/ で始まるパスだけ、転送先は api2.cursor.sh だけにして、何でも中継する踏み台にされないようにした

Workerのコード

export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (request.method !== "POST") return new Response("pebble-grok-bridge ok");
    if (!url.pathname.startsWith("/automations/webhook/")) return new Response("not found", { status: 404 });
    const auth = request.headers.get("Authorization");
    if (!auth) return new Response("missing Authorization", { status: 401 });
    const ct = request.headers.get("content-type") || "";
    let raw = {}; let text = "";
    try {
      if (ct.includes("json")) { raw = await request.json(); }
      else if (ct.includes("form")) {
        const form = await request.formData();
        for (const [k, v] of form.entries()) raw[k] = typeof v === "string" ? v : "[file: " + (v.name || k) + "]";
      } else { raw = { body: await request.text() }; }
      text = raw.transcription || raw.text || raw.transcript || raw.body || "";
    } catch (e) { text = "(読み取り失敗) " + e.message; }
    const payload = {
      source: "pebble", text, test: raw.test === "true" || request.headers.get("X-Index-Trigger") === "test-event",
      trigger: request.headers.get("X-Index-Trigger"), delivery: request.headers.get("X-Index-Delivery"),
      recordedAt: raw.recordedAt ? new Date(Number(raw.recordedAt)).toISOString() : null,
      receivedAt: new Date().toISOString(), raw
    };
    const res = await fetch("https://api2.cursor.sh" + url.pathname, {
      method: "POST", headers: { "Content-Type": "application/json", "Authorization": auth }, body: JSON.stringify(payload)
    });
    return new Response(await res.text(), { status: res.status });
  }
};

録音のファイルが届いたときは、中身を転送せず、ファイル名の印だけを残します。

Grok Botから返ってきた状態コードは、そのままPebbleに返します。

手元で動かして確かめたこと

このコードを手元のNode.jsで動かし、Pebbleと同じ形のフォームを送って、転送される中身を確かめました。

送ったもの結果
テスト送信と同じフォーム(録音のファイル付き)JSONに直して api2.cursor.sh の同じパスへ転送。録音は [file: ファイル名] に置き換わる
別のパスへのPOST404を返し、転送しない
Authorizationが無いPOST401を返し、転送しない
転送先への送信は差し替えて、中身だけを見た(2026年11月4日)

Grok Botに届くJSONは、次の形になります。

{
  "source": "pebble",
  "text": "Index webhook test event",
  "test": true,
  "trigger": "test-event",
  "delivery": "(送信ごとのID)",
  "recordedAt": "(録音した時刻)",
  "receivedAt": "(Workerが受け取った時刻)",
  "raw": { "transcription": "Index webhook test event", "recordedAt": "…", "client": "ring", "test": "true" }
}

デプロイのしかた

Cloudflareのダッシュボードから作るなら、流れは次のとおりです。

メニューの名前は変わることがあるので、画面に合わせて読み替えてください。

  1. Cloudflareのダッシュボードで「Workers & Pages」を開く
  2. 「Create」から、Hello Worldのひな形で新しいWorkerを作る
  3. 名前(例:pebble-grok-bridge)を付けてデプロイする
  4. 「Edit code」で中身を上のコードに置き換え、もう一度デプロイする
  5. 表示された https://pebble-grok-bridge.<サブドメイン>.workers.dev を控える

私の場合は、Grok BotにCloudflareのコネクタをつないで許可しただけで、Workerの作成からデプロイまでGrok Botがやってくれました。

自分で触ったのは、許可のボタンくらいです。

手順3:Pebbleの設定を書き換える

Workerができたら、PebbleアプリのWebhookの設定を次のように直します。

項目設定する値
Webhook URLhttps://pebble-grok-bridge.<サブドメイン>.workers.dev/automations/webhook/<ID>(IDはGrok BotのURLの末尾と同じ)
HeadersContent-Typeの行は消し、名前 Authorization・値 Bearer crsr_… の1行だけにする
What to sendTranscription
筆者の設定(2026年11月4日)

手順4:テストして保存する

「Send test event」を押すと、200 OK(1083ミリ秒)が返りました。

Send test eventで200 OKが返った画面。Recent runsには2時間前の415 ERRORが2回残っている
テスト送信で200 OKが返った画面(2026/11/04時点の表示)

Grok Botのチャットにも、「テストイベントを受信できました。本文は『Index webhook test event』」という報告が届いています。

テスト送信の文字起こしには、決まった文章が入る仕様です。

私のルーティンでは、テスト送信のときは振り分けをせず、受け取れたことだけを報告するようにしています。

最後に「Save」で設定を保存します。

Pebbleは、URLを入れて保存した押し方からしか送らないので、保存を忘れると何も届きません。

振り分けるルーティンの指示

Grok Botのルーティンには、次の指示を入れています。

  1. test が true なら、テストを受け取れたことだけ報告して終わる
  2. 届いたJSONから本文(text、無ければ raw.transcription)を取り出す
  3. 内容から担当のBot(WEB制作・AI BLOG・blog・ASPの管理・ポイ活・YouTube・LINEスタンプなど)を選ぶ
  4. 選んだBotに、依頼の内容をそのまま渡す
  5. 送信・購入・削除など取り消せない作業は、私の確認をとってから
  6. 結果は1〜2文で短く報告する

チャットで頼むときは、たとえば次のように書きます。

Webhookで起動するルーティンを作って。
名前は「Pebble受付」。
届くJSONの text が、私がリングに話した内容。
1. test が true なら、受信できたことだけ報告して終わる
2. text の内容から、担当のBot(WEB制作・AI BLOG・blog・ASPの管理・ポイ活・YouTube・LINEスタンプ)を1つ選ぶ
3. 選んだBotに、text をそのまま依頼として渡す
4. 送信・購入・削除など取り消せない作業は、私の確認をとってから
5. 結果は1〜2文で報告する
6. 同じ delivery が2回届いたら、2回目は何もしない

6番目の重複の扱いは、次の節で説明する送り直しに備えたものです。

本番で使ってみた:リングの依頼がYouTubeのBotに届いた

設定した翌日の2026年11月5日、リングに「YouTubeで、Web制作会社として数字が伸びてバズりやすいテーマを調べて並べて」と話しました。

PebbleアプリのRecent runsには、文字起こしの送信が200 OK(473バイト・2847ミリ秒)で残っています。

PebbleアプリのRecent runs。本番の文字起こしの送信が200 OK(473 B・2847 ms)、その下にテスト送信の200 OKと415 ERRORが残っている
本番の送信も200 OKになった(2026/11/05時点の表示)

話した1分後には、Grok Botから「YouTubeのBotに回しました」という報告が届きました。

Grok Botの報告。リングから届いたメモをYouTubeのBotに回したと書かれている
Grok Botが依頼をYouTubeのBotに振り分けた報告(2026/11/05時点の表示)

依頼を受けたYouTubeのBotは調べものを始め、伸びている動画の例と再生数を添えて、テーマの候補を返してきました。

YouTubeのBotの返答。Web制作会社のチャンネルで伸びやすいテーマを、実際の動画の再生数とともに並べている
YouTubeのBotから届いた調査の結果(一部。2026/11/05時点の表示)

ルーティンに書いた振り分けの指示どおりに、担当のBotまで依頼が渡っています。

文字起こしの聞き違いはGrok Botが補ってくれた

Pebbleアプリに残った文字起こしより、Grok Botが受け取って言い直した依頼のほうが正確でした。

文字起こしに聞き違いがあっても、Grok Botが前後の文脈から意図を読み取り、正しい内容でYouTubeのBotに渡しています。

私のPebbleアプリでは、音声の認識に「parakeet-tdt-0.6b-v3」というモデルが選ばれていました。

文字起こしが完全でなくても依頼が伝わるので、言い回しを気にせず、思いついたまま話せます。

使う前に知っておきたいこと

鍵をどこに置くか

私の方法では、Grok Botの鍵をPebbleアプリのヘッダーに入れ、Workerは中継するだけです。

GitHubのZhigre/pebble-index-bridgeは逆で、Grok Botの鍵は中継役のサーバーに置き、Pebbleには別に作った鍵だけを入れます。

筆者の方法pebble-index-bridge
中継役Cloudflare Worker自分のサーバーのDocker
Grok Botの鍵の置き場所Pebbleアプリのヘッダー中継役のサーバー
Pebbleに入れる鍵Grok Botの鍵そのもの中継役のための別の鍵
中継役が持つ秘密なしGrok Botの鍵
スマホの設定が漏れたときGrok Botの鍵を作り直す中継役の鍵だけ作り直せばよい
準備の手間Workerを1つ作るだけサーバー・Docker・公開用の通り道が要る
筆者の設定と pebble-index-bridge のREADMEより(2026年11月4日確認)

どちらが良いかは、どこに秘密を置きたいか次第です。

鍵が漏れたと思ったら、Grok Botにルーティンの鍵を作り直してもらい、Pebbleの設定も書き換えます。

失敗すると送り直され、重複することがある

Pebbleは、通信の失敗や408・429・5xxの応答のとき、1分ごとに最大10回まで送り直します。

415のような4xxは送り直されないので、最初の415は、Recent runsに失敗が残るだけでした。

WorkerはGrok Botの応答をそのまま返すため、Grok Bot側が5xxを返すと、Pebbleが同じ録音を送り直します。

同じ録音のX-Index-Deliveryは何度送っても同じなので、WorkerがJSONの delivery に入れたこの値で、ルーティン側が重複を見分けられます。

ルーティンの回数と使用量

Grok Botのルーティンが動ける回数は、すべてを合わせて1時間に300回、1つのルーティンでは1時間に200回までです。

超えると「Automation run rate limit exceeded」で失敗し、自動では送り直されません。

ルーティンは、1回動くたびに週の使用量が減る仕組みです。

テスト送信でもルーティンは実際に動くため、試す回数は少なめにしておくと安心です。

さらに安全にするなら:署名を検証する

Pebbleの「Sign requests」をオンにすると、HMAC-SHA256の署名がヘッダーに付きます。

Worker側でこの署名を確かめると、Pebble以外から届いた要求を断れるのが利点です。

私はAuthorizationの鍵で足りると判断して入れていませんが、URLと鍵を人に見せる機会がある人には向いています。

公式ドキュメントの手順をWorkers向けに書いたのが、次のコードです。

async function verifyIndexSignature(request, rawBody, secret) {
  const h = (name) => request.headers.get(name) || "";
  const version = h("X-Index-Webhook-Version");
  const timestamp = h("X-Index-Timestamp");
  const signature = h("X-Index-Signature");
  if (version !== "1" || !/^\d+$/.test(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  if (!/^[0-9a-f]{64}$/.test(signature)) return false;
  const isTest = h("X-Index-Test") === "true" ? "1" : "0";
  const prefix = new TextEncoder().encode(
    `v${version}\n${timestamp}\n${h("X-Index-Delivery")}\n${h("X-Index-Trigger")}\n${isTest}\n`
  );
  const signed = new Uint8Array(prefix.length + rawBody.byteLength);
  signed.set(prefix);
  signed.set(new Uint8Array(rawBody), prefix.length);
  const key = await crypto.subtle.importKey(
    "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["verify"]
  );
  const sigBytes = new Uint8Array(signature.match(/../g).map((x) => parseInt(x, 16)));
  return crypto.subtle.verify("HMAC", key, sigBytes, signed);
}

Workerの本体には、次のように差し込みます。

// fetch(request) を fetch(request, env) にして、本文を読む前にこう差し込む
const rawBody = await request.arrayBuffer();
if (!(await verifyIndexSignature(request, rawBody, env.INDEX_WEBHOOK_SECRET))) {
  return new Response("invalid signature", { status: 401 });
}
// フォームは、先に読んだ本文から作り直して読む
const form = await new Response(rawBody, { headers: { "content-type": ct } }).formData();

署名は届いた本文そのものに対して計算されるので、フォームとして読む前に、本文をそのままの形で読みます。

署名の鍵には、Pebbleアプリに入れたものと同じ値を使い、Workerのシークレット(例の名前は INDEX_WEBHOOK_SECRET)に登録しておく形です。

公式ドキュメントは、32バイト以上のランダムな値を勧めています。

このコードは、公式の手順どおりに署名を付けた要求を作り、手元のNode.jsで確かめました。

試した要求結果
正しい署名通る
テスト送信の形(X-Index-Test: true)通る
本文を1バイト変えた断る
違う鍵で署名した断る
10分前の時刻断る(5分を超えたら断る)
手元のNode.jsで確かめた結果(2026年11月4日)

使い方のアイデア

  • 移動中に思いついたブログのネタを、blogのBotにメモする
  • 「〇〇さんのサイト、ここを直す」と、WEB制作のBotに作業を登録する
  • ポイ活のキャンペーンの確認を、ポイ活のBotに頼む
  • YouTubeの企画やLINEスタンプのアイデアを、思いついたその場で残す

スマホを取り出さなくていいので、思いつきを逃さないのがいちばんの良さです。

Grok Botのほかに、話しかけて仕事を任せられるAIのボットはAIボット4つの比較で比べています。

この記事で確かめた範囲

  • 確認日:公式ドキュメントとヘルプは2026年11月4日に読んだものです
  • 見たもの:Pebble公式のWebhooksとIndex 01、Grok BotヘルプのRoutines、CloudflareのWorkersの制限(いずれも英語)
  • 実際に試したこと:自分のPebble Index 01とGrok Botをつなぎ、415から200 OKになるまでと、11月5日の本番の依頼がYouTubeのBotに届くまで
  • 手元で確かめたこと:記事に載せたWorkerのコードと署名検証のコードを、Pebbleと同じ形の要求で動かした結果

画面の名前や仕様は変わることがあるので、設定の前に公式の資料も合わせて見てください。

スポンサーリンク

まとめ

  • Pebble Index 01は、話した内容の文字起こしをWebhookで送れるので、Grok Botのルーティンにつなげられる
  • Pebbleはフォーム形式でしか送らず、Grok BotのWebhookはJSONの本文を受け取るので、直接つなぐと415になる
  • Cloudflare Workerを挟んでJSONに直せば通る(Workerには秘密を持たせず、転送先も固定する)
  • Pebbleは失敗すると送り直すので、ルーティン側で delivery を見て重複を除く
  • 本番では、リングで話した依頼がYouTubeのBotまで届き、文字起こしの聞き違いもGrok Botが補った
  • ルーティンは動くたびに使用量を使い、1時間あたりの回数にも上限がある
  • 鍵は人に見せず、スクリーンショットではURLと鍵をぼかす

リングに話しかけるだけで仕事が進むのは、思った以上に快適です。

Pebble Index 01を持っている人は、試してみてください。

公式の資料は、次のページです。

Webhooks(Pebble Developers)

Routines(Grok Bot ヘルプ)

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

須澤 元貴のアバター 須澤 元貴 代表社員

MOTOKI合同会社 代表社員。
アニメ・映像業界とゲーム業界で、2D・UI・3D・アニメーション制作、映像編集、ツール制作に携わってきました。
現在はWordPressを中心にWebサイトの制作とカスタマイズを手がけています。
著書に『Unity デザイナーズ・バイブル』『Unityゲーム プログラミング・バイブル 2nd Generation』(ボーンデジタル)。
岡山県出身、埼玉県在住。

次に学ぶ・作業環境を選ぶ

学習を続けたい方や、作業環境を整えたい方は、目的に合うガイドをご覧ください。

生成AIのおすすめ書籍

周辺機器の優先度

目次