@rubytech/taskmaster 1.17.6 → 1.17.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@
6
6
  <title>Taskmaster Control</title>
7
7
  <meta name="color-scheme" content="dark light" />
8
8
  <link rel="icon" type="image/png" href="./favicon.png" />
9
- <script type="module" crossorigin src="./assets/index-koe4eKhk.js"></script>
9
+ <script type="module" crossorigin src="./assets/index-ebUt9xoF.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="./assets/index-XqRo9tNW.css">
11
11
  </head>
12
12
  <body>
@@ -92,6 +92,7 @@ export async function executeJob(state, job, nowMs, opts) {
92
92
  status,
93
93
  error: err,
94
94
  summary,
95
+ outputText,
95
96
  runAtMs: startedAt,
96
97
  durationMs: job.state.lastDurationMs,
97
98
  nextRunAtMs: job.state.nextRunAtMs,
@@ -234,7 +234,7 @@ function extractMediaPrefixRefs(text) {
234
234
  }
235
235
  return refs;
236
236
  }
237
- function mediaRefToUrl(ref, workspaceRoot) {
237
+ function mediaRefToUrl(ref, workspaceRoot, agentId) {
238
238
  const relPath = nodePath.relative(workspaceRoot, ref.absPath);
239
239
  // Must stay within workspace (no ../ escapes)
240
240
  if (relPath.startsWith("..") || nodePath.isAbsolute(relPath))
@@ -248,7 +248,8 @@ function mediaRefToUrl(ref, workspaceRoot) {
248
248
  catch {
249
249
  /* file may not exist yet */
250
250
  }
251
- return `/api/media?path=${encodeURIComponent(relPath)}${mtime}`;
251
+ const agentParam = agentId ? `&agent=${encodeURIComponent(agentId)}` : "";
252
+ return `/api/media?path=${encodeURIComponent(relPath)}${agentParam}${mtime}`;
252
253
  }
253
254
  function stripBase64FromContentBlocks(content) {
254
255
  let changed = false;
@@ -309,7 +310,7 @@ export function stripBase64ImagesFromMessages(messages) {
309
310
  *
310
311
  * Must be called BEFORE stripEnvelopeFromMessages (which strips annotations).
311
312
  */
312
- export function sanitizeMediaForChat(messages, workspaceRoot) {
313
+ export function sanitizeMediaForChat(messages, workspaceRoot, agentId) {
313
314
  if (messages.length === 0 || !workspaceRoot) {
314
315
  // No workspace context — fall back to plain base64 stripping
315
316
  return stripBase64ImagesFromMessages(messages);
@@ -325,14 +326,14 @@ export function sanitizeMediaForChat(messages, workspaceRoot) {
325
326
  const role = typeof entry?.role === "string" ? entry.role.toLowerCase() : "";
326
327
  if (role === "user")
327
328
  seenPaths.clear();
328
- const result = sanitizeMessageMedia(message, workspaceRoot, seenPaths);
329
+ const result = sanitizeMessageMedia(message, workspaceRoot, seenPaths, agentId);
329
330
  if (result !== message)
330
331
  changed = true;
331
332
  return result;
332
333
  });
333
334
  return changed ? next : messages;
334
335
  }
335
- function sanitizeMessageMedia(message, workspaceRoot, seenPaths) {
336
+ function sanitizeMessageMedia(message, workspaceRoot, seenPaths, agentId) {
336
337
  if (!message || typeof message !== "object")
337
338
  return message;
338
339
  const entry = message;
@@ -350,7 +351,7 @@ function sanitizeMessageMedia(message, workspaceRoot, seenPaths) {
350
351
  for (const ref of mediaRefs) {
351
352
  if (seenPaths.has(ref.absPath))
352
353
  continue;
353
- const url = mediaRefToUrl(ref, workspaceRoot);
354
+ const url = mediaRefToUrl(ref, workspaceRoot, agentId);
354
355
  if (!url)
355
356
  continue;
356
357
  seenPaths.add(ref.absPath);
@@ -112,7 +112,10 @@ export function handleMediaRequest(req, res, opts) {
112
112
  res.end("Forbidden");
113
113
  return true;
114
114
  }
115
- const workspaceRoot = resolveWorkspaceRoot(opts.config);
115
+ // Resolve workspace root for the requested agent (supports multi-account).
116
+ // Falls back to the default "admin" agent when no agent param is provided.
117
+ const agentParam = urlObj.searchParams.get("agent") ?? "admin";
118
+ const workspaceRoot = resolveAgentWorkspaceRoot(opts.config, agentParam);
116
119
  const filePath = path.resolve(workspaceRoot, relPath);
117
120
  // Boundary check: must stay within workspace
118
121
  if (!filePath.startsWith(workspaceRoot + path.sep) && filePath !== workspaceRoot) {
@@ -25,7 +25,7 @@
25
25
  import { randomUUID } from "node:crypto";
26
26
  import fs from "node:fs";
27
27
  import path from "node:path";
28
- import { resolveAgentWorkspaceDir, resolveSessionAgentId } from "../agents/agent-scope.js";
28
+ import { resolveAgentWorkspaceDir, resolveAgentWorkspaceRoot, resolveSessionAgentId } from "../agents/agent-scope.js";
29
29
  import { loadOrCreateSessionSecret, signSessionKey, verifySessionToken, } from "./public-chat/session-token.js";
30
30
  import { resolveEffectiveMessagesConfig, resolveIdentityName } from "../agents/identity.js";
31
31
  import { dispatchInboundMessage } from "../auto-reply/dispatch.js";
@@ -41,7 +41,6 @@ import { deliverOtp, detectOtpChannels, hasPhoneMethod, normalizeVerifyMethods,
41
41
  import { buildPublicSessionKey, resolvePublicAgentId } from "./public-chat/session.js";
42
42
  import { loadSessionEntry, readSessionMessages } from "./session-utils.js";
43
43
  import { extractFileAttachments, sanitizeMediaForChat, stripEnvelopeFromMessages, } from "./chat-sanitize.js";
44
- import { resolveWorkspaceRoot } from "./media-http.js";
45
44
  import { readJsonBodyOrError, sendInvalidRequest, sendJson, sendMethodNotAllowed, setSseHeaders, writeDone, } from "./http-common.js";
46
45
  // ---------------------------------------------------------------------------
47
46
  // Helpers
@@ -536,7 +535,7 @@ async function handleChat(req, res, _accountId, cfg, maxBodyBytes) {
536
535
  }));
537
536
  }
538
537
  // Extract file attachments from tool results in this session
539
- const workspaceRoot = resolveWorkspaceRoot(cfg);
538
+ const workspaceRoot = resolveAgentWorkspaceRoot(cfg, agentId);
540
539
  const { entry: sessionEntry, storePath } = loadSessionEntry(sessionKey);
541
540
  let attachments = [];
542
541
  if (sessionEntry?.sessionId && storePath) {
@@ -691,7 +690,7 @@ async function handleChat(req, res, _accountId, cfg, maxBodyBytes) {
691
690
  if (!closed) {
692
691
  // Emit file attachments (e.g. PDFs from document_to_pdf) before closing
693
692
  try {
694
- const workspaceRoot = resolveWorkspaceRoot(cfg);
693
+ const workspaceRoot = resolveAgentWorkspaceRoot(cfg, agentId);
695
694
  const { entry: sessionEntry, storePath } = loadSessionEntry(sessionKey);
696
695
  if (sessionEntry?.sessionId && storePath) {
697
696
  const sessionMsgs = readSessionMessages(sessionEntry.sessionId, storePath, sessionEntry.sessionFile);
@@ -748,8 +747,9 @@ async function handleChatHistory(req, res) {
748
747
  const limitParam = url.searchParams.get("limit");
749
748
  const requested = limitParam ? Math.min(10_000, Math.max(1, Number(limitParam) || 5000)) : 5000;
750
749
  const messages = rawMessages.length > requested ? rawMessages.slice(-requested) : rawMessages;
751
- const workspaceRoot = resolveWorkspaceRoot(cfg);
752
- const sanitized = stripEnvelopeFromMessages(sanitizeMediaForChat(messages, workspaceRoot));
750
+ const historyAgentId = resolveSessionAgentId({ sessionKey, config: cfg });
751
+ const workspaceRoot = resolveAgentWorkspaceRoot(cfg, historyAgentId);
752
+ const sanitized = stripEnvelopeFromMessages(sanitizeMediaForChat(messages, workspaceRoot, historyAgentId));
753
753
  sendJson(res, 200, {
754
754
  session_key: sessionKey,
755
755
  messages: sanitized,
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { CURRENT_SESSION_VERSION } from "@mariozechner/pi-coding-agent";
5
- import { resolveAgentWorkspaceDir, resolveSessionAgentId } from "../../agents/agent-scope.js";
5
+ import { resolveAgentWorkspaceDir, resolveAgentWorkspaceRoot, resolveSessionAgentId } from "../../agents/agent-scope.js";
6
6
  import { resolveEffectiveMessagesConfig, resolveIdentityName } from "../../agents/identity.js";
7
7
  import { resolveThinkingDefault } from "../../agents/model-selection.js";
8
8
  import { resolveAgentTimeoutMs } from "../../agents/timeout.js";
@@ -16,7 +16,6 @@ import { abortChatRunById, abortChatRunsForSessionKey, isChatStopCommandText, re
16
16
  import { ErrorCodes, errorShape, formatValidationErrors, validateChatAbortParams, validateChatHistoryParams, validateChatInjectParams, validateChatSendParams, } from "../protocol/index.js";
17
17
  import { loadSessionEntry, readSessionMessages, resolveSessionModelRef } from "../session-utils.js";
18
18
  import { sanitizeMediaForChat, stripEnvelopeFromMessages } from "../chat-sanitize.js";
19
- import { resolveWorkspaceRoot } from "../media-http.js";
20
19
  import { formatForLog } from "../ws-log.js";
21
20
  import { fireSuggestion } from "../../suggestions/broadcast.js";
22
21
  function resolveTranscriptPath(params) {
@@ -195,8 +194,9 @@ export const chatHandlers = {
195
194
  const end = totalMessages - skip;
196
195
  const start = Math.max(0, end - max);
197
196
  const messages = start === 0 && end === totalMessages ? rawMessages : rawMessages.slice(start, end);
198
- const workspaceRoot = resolveWorkspaceRoot(cfg);
199
- const withMediaUrls = sanitizeMediaForChat(messages, workspaceRoot);
197
+ const historyAgentId = resolveSessionAgentId({ sessionKey, config: cfg });
198
+ const workspaceRoot = resolveAgentWorkspaceRoot(cfg, historyAgentId);
199
+ const withMediaUrls = sanitizeMediaForChat(messages, workspaceRoot, historyAgentId);
200
200
  const sanitized = preserveEnvelopes ? withMediaUrls : stripEnvelopeFromMessages(withMediaUrls);
201
201
  // Diagnostic: log resolution details so we can trace "lost history" reports.
202
202
  const prevCount = entry?.previousSessions?.length ?? 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.17.6",
3
+ "version": "1.17.7",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,29 +1,38 @@
1
1
  ---
2
2
  name: stripe
3
- description: Guide users through getting Stripe API credentials the secret key for payment links and the webhook signing secret for automatic post-payment workflows.
3
+ description: Stripe integration set up API credentials (secret key + webhook signing secret), create payment links via Checkout Sessions, and handle webhook events for automatic post-payment workflows.
4
4
  metadata: {"taskmaster":{"emoji":"💳"}}
5
5
  ---
6
6
 
7
- # Stripe Setup
7
+ # Stripe Integration
8
8
 
9
- Walks users through creating a Stripe account (or logging in to an existing one), obtaining their secret API key, setting up a webhook endpoint, and saving both credentials. One guided flow covers both keys — the secret key enables payment link creation; the webhook signing secret enables automatic post-payment workflow dispatch without any manual user step.
9
+ Handles Stripe credential setup and payment operations. One guided flow covers both credentials — the secret key enables payment link creation; the webhook signing secret enables automatic post-payment workflow dispatch without requiring any manual step from the customer.
10
10
 
11
11
  ## When to activate
12
12
 
13
- - User asks about Stripe setup or connecting Stripe to Taskmaster
14
- - Control panel shows Stripe or Stripe Webhook Secret as unconfigured and payment features are in use
13
+ - User asks about connecting Stripe or setting up payments
14
+ - `stripe` or `stripe_webhook_secret` API key slots are empty and payment features are in use
15
15
  - BOOTSTRAP detects missing Stripe key when payment link generation is needed
16
16
  - User asks why payments aren't working or why post-payment automation isn't firing
17
17
 
18
18
  ## What it unlocks
19
19
 
20
- - **Payment link generation** — create Stripe Checkout Sessions for collecting booking fees or other payments
21
- - **Automatic post-payment dispatch** — Taskmaster receives `checkout.session.completed` webhook events and triggers follow-up workflows without requiring the customer to manually confirm payment
20
+ - **Payment link generation** — create Stripe Checkout Sessions for collecting fees or payments
21
+ - **Automatic post-payment dispatch** — Taskmaster receives webhook events and triggers follow-up workflows without any manual customer step
22
+
23
+ ## Key Storage
24
+
25
+ | Slot | Value | Purpose |
26
+ |------|-------|---------|
27
+ | `stripe` | `sk_test_...` or `sk_live_...` | Secret key for API calls (creating Checkout Sessions, verifying payments) |
28
+ | `stripe_webhook_secret` | `whsec_...` | Signing secret for verifying incoming webhook events are genuinely from Stripe |
29
+
30
+ Both stored via `api_keys` tool. Check `api_keys` before starting — skip any step where a credential already exists.
22
31
 
23
32
  ## References
24
33
 
25
34
  | Task | When to use | Reference |
26
35
  |------|-------------|-----------|
27
- | Guided setup | User wants help getting both credentials | `references/setup-guide.md` |
36
+ | Credential setup | `stripe` or `stripe_webhook_secret` slot is empty | `references/setup-guide.md` |
28
37
 
29
38
  Load the reference and follow its instructions.
@@ -1,156 +1,102 @@
1
- # Stripe — Guided Setup
1
+ # Stripe — Setup Guide
2
2
 
3
- Walk the user through getting both Stripe credentials: the secret API key (used to create payment links) and the webhook signing secret (used to verify incoming payment confirmation events). Guide with clear step-by-step instructions.
3
+ Walk the user through getting both Stripe credentials: the secret API key and the webhook signing secret.
4
4
 
5
- **Important:** Stripe's dashboard is dense and aimed at developers. The user only needs two things: their secret key and a webhook endpoint. Guide them directly to both — do not let them get lost in the product catalogue, reports, or analytics.
5
+ ## Before You Start
6
6
 
7
- ---
7
+ 1. Check `api_keys` to see which slots are already filled — skip any step where a credential exists
8
+ 2. Check memory (e.g. `memory/admin/infrastructure.md`) for the Tailscale/public URL — do NOT ask the user for it
9
+ 3. If the URL is not in memory, construct it as `https://{tailscale-or-public-host}/webhook/stripe`
8
10
 
9
- ## Prerequisites
10
-
11
- - User has a Stripe account, or is ready to create one
12
- - Taskmaster's public URL — the user needs to know their hostname to construct the webhook endpoint URL (e.g. `https://taskmaster-19000.local:19000` or their public domain)
13
-
14
- ---
15
-
16
- ## Step 1: Explain
17
-
18
- Tell the user what you are setting up and why both keys are needed:
11
+ ## What to tell the user
19
12
 
20
13
  > "To take payments, Taskmaster needs two things from your Stripe account:
21
14
  >
22
15
  > 1. **Secret key** — lets Taskmaster create payment links on your behalf
23
- > 2. **Webhook signing secret** — tells Taskmaster when a customer has actually paid, so it can automatically trigger follow-up actions (like releasing booking details) without waiting for the customer to say anything
16
+ > 2. **Webhook signing secret** — tells Taskmaster when a customer has actually paid, so it can automatically trigger follow-up actions without waiting for the customer to say anything
24
17
  >
25
18
  > This takes about 5 minutes. I'll walk you through both."
26
19
 
27
- ---
20
+ ## Step 1: Log in to Stripe
28
21
 
29
- ## Step 2: Log in to Stripe
22
+ > "Go to **dashboard.stripe.com** and sign in. If you don't have an account yet, click **Start now** — it's free to set up."
30
23
 
31
- > "Go to **dashboard.stripe.com** and sign in. If you don't have an account yet, click **Start now** and create one — it's free to set up.
32
- >
33
- > Let me know when you're in the dashboard."
34
-
35
- Wait for the user to confirm.
24
+ Wait for confirmation.
36
25
 
37
- ---
38
-
39
- ## Step 3: Get the secret key
26
+ ## Step 2: Get the secret key
40
27
 
41
28
  **Navigation: Dashboard → Developers → API keys**
42
29
 
43
- > "In the left sidebar, click **Developers** (near the bottom), then click **API keys**.
30
+ > "In the left sidebar, click **Developers**, then **API keys**.
44
31
  >
45
32
  > You'll see two keys:
46
- > - **Publishable key** — starts with `pk_` — we don't need this one
47
- > - **Secret key** — starts with `sk_` — this is the one we need
33
+ > - **Publishable key** (`pk_...`) — we don't need this
34
+ > - **Secret key** (`sk_...`) — this is the one
48
35
  >
49
- > Click **Reveal live key** (or **Reveal test key** if you're still testing). Copy the full key — it starts with `sk_live_` or `sk_test_` and is quite long. Send it to me."
50
-
51
- Wait for the user to send the key.
36
+ > Click **Reveal** next to the secret key. Copy the full value and send it to me."
52
37
 
53
- Verify format (`sk_test_` or `sk_live_` prefix, 30+ characters). If it looks wrong:
38
+ Validate: must start with `sk_test_` or `sk_live_`, 30+ characters.
54
39
 
55
- > "That doesn't look quite right the secret key should start with `sk_test_` (for test mode) or `sk_live_` (for production) and be at least 30 characters long. Can you check you've copied the **Secret key**, not the Publishable key?"
40
+ Store: `api_keys({ action: "set", provider: "stripe", apiKey: "<key>" })`
56
41
 
57
- Once you have a valid key, save it:
42
+ ## Step 3: Create the webhook endpoint
58
43
 
59
- ```
60
- api_keys({ action: "set", provider: "stripe", apiKey: "<the key>" })
61
- ```
44
+ **Navigation: Dashboard → Developers → Webhooks → + Add destination**
62
45
 
63
- Confirm to the user:
46
+ Look up the webhook URL from memory before this step. If not found, construct it as `https://{tailscale-url}/webhook/stripe`.
64
47
 
65
- > "Secret key saved. Now let's set up the webhook so Taskmaster gets notified automatically when a payment goes through."
66
-
67
- ---
68
-
69
- ## Step 4: Create the webhook endpoint
70
-
71
- **Navigation: Dashboard → Developers → Webhooks → Add endpoint**
72
-
73
- > "Still in the Developers section, click **Webhooks** in the left sidebar.
74
- >
75
- > Click **Add endpoint** (top-right of the page).
76
- >
77
- > In the **Endpoint URL** field, enter:
78
- > `https://<your-taskmaster-host>/webhook/stripe`
79
- >
80
- > Replace `<your-taskmaster-host>` with your Taskmaster address — for example `taskmaster-19000.local:19000` if you're on your local network, or your public domain if Taskmaster is accessible from the internet.
48
+ > "Still in Developers, click **Webhooks**, then **+ Add destination**.
81
49
  >
82
- > Under **Select events**, click **+ Select events**, search for `checkout.session.completed`, tick it, and click **Add events**.
50
+ > First, select events. Expand **Checkout** and tick **Select all Checkout events** (4 events: completed, expired, async_payment_failed, async_payment_succeeded). Click **Continue**.
83
51
  >
84
- > Then click **Add endpoint** to save.
52
+ > On the next screen:
53
+ > - **Destination name:** something that identifies this service
54
+ > - **Endpoint URL:** `{webhook_url}`
55
+ > - **Description:** optional
85
56
  >
86
- > Let me know when you've done that."
57
+ > Click **Create destination**."
87
58
 
88
- If the user is unsure of their Taskmaster address:
59
+ Wait for confirmation.
89
60
 
90
- > "Your Taskmaster address is the URL you use to access the control panel — just the host and port, without any path. For example, if your control panel is at `http://taskmaster-19000.local:19000`, your webhook URL would be `https://taskmaster-19000.local:19000/webhook/stripe`."
61
+ ## Step 4: Get the webhook signing secret
91
62
 
92
- Wait for the user to confirm the endpoint is created.
63
+ > "On the endpoint page you just created, find the **Signing secret** section. Click **Reveal**, then copy the `whsec_...` value and send it to me."
93
64
 
94
- ---
65
+ Validate: must start with `whsec_`.
95
66
 
96
- ## Step 5: Get the webhook signing secret
67
+ Store: `api_keys({ action: "set", provider: "stripe_webhook_secret", apiKey: "<key>" })`
97
68
 
98
- > "After creating the endpoint, Stripe shows a **Signing secret** — it starts with `whsec_`. Click **Reveal** to show it, then copy it and send it to me.
99
- >
100
- > If you've already navigated away, click on the endpoint you just created in the Webhooks list — the signing secret is shown on that endpoint's detail page."
101
-
102
- Wait for the user to send the signing secret.
103
-
104
- Verify format (`whsec_` prefix). If it looks wrong:
105
-
106
- > "The webhook signing secret should start with `whsec_` — that's different from the API key. Click on your webhook endpoint in the Webhooks list and look for the **Signing secret** section."
107
-
108
- Once you have a valid signing secret, save it:
109
-
110
- ```
111
- api_keys({ action: "set", provider: "stripe_webhook_secret", apiKey: "<the whsec key>" })
112
- ```
113
-
114
- ---
69
+ ## Step 5: Confirm
115
70
 
116
- ## Step 6: Confirm
117
-
118
- > "Both Stripe credentials are saved:
119
- >
120
- > - **Secret key** — payment links can now be created
121
- > - **Webhook signing secret** — Taskmaster will receive and verify payment confirmations automatically
71
+ > "Both Stripe credentials saved:
72
+ > - **Secret key** — payment links ready
73
+ > - **Webhook signing secret** — automatic payment notifications active
122
74
  >
123
- > You're all set. As soon as a customer completes a payment, Taskmaster will be notified and can trigger follow-up actions immediately — no manual step needed from the customer."
124
-
125
- ---
126
-
127
- ## If the user already has a Stripe account
75
+ > When a customer pays, Taskmaster will be notified automatically and trigger follow-up actions immediately."
128
76
 
129
- Skip Step 2 (they're already logged in). Proceed from Step 3.
77
+ Store webhook endpoint details in memory (endpoint URL, destination name, events subscribed to) for future reference.
130
78
 
131
- If they already have a secret key saved somewhere, skip to Step 4.
79
+ ## Test vs Live Mode
132
80
 
133
- ---
134
-
135
- ## Test mode vs live mode
136
-
137
- | | Test mode | Live mode |
81
+ | | Test | Live |
138
82
  |---|---|---|
139
83
  | Key prefix | `sk_test_` | `sk_live_` |
140
- | Webhooks | Use test webhook endpoints | Use live webhook endpoints |
141
- | Payments | No real money moves | Real payments |
84
+ | Webhook secret | Same `whsec_` format | Same `whsec_` format |
85
+ | Payments | No real money | Real payments |
86
+
87
+ Start with test mode. Switch to live keys when ready — just update both API key slots.
142
88
 
143
- Start with test mode (`sk_test_`) if the user is still building or testing their flow. Switch to live keys when ready for real payments. Both key types are stored the same way — just replace the value in the API Keys panel.
89
+ ## Tailscale/Funnel Note
144
90
 
145
- ---
91
+ Stripe's servers need to reach the webhook URL from the public internet. Standard Tailscale URLs are only accessible within the tailnet. **Tailscale Funnel** must be enabled on the host node for Stripe to deliver events. In test mode the endpoint can be created and tested later. Flag this to the user if webhook delivery failures occur.
146
92
 
147
93
  ## Troubleshooting
148
94
 
149
95
  | Problem | Solution |
150
96
  |---------|----------|
151
- | Can't find Developers menu | It's in the left sidebar, near the bottom. If you're on the Stripe home page, you may need to scroll down or look for a collapsed sidebar — click the hamburger or expand icon. |
152
- | Secret key is hidden / "Restricted key" shown | You may be looking at a restricted key. Click **API keys** in the Developers section — the secret key is under **Standard keys**. |
153
- | Can't find signing secret after creating endpoint | Click on the endpoint row in the Webhooks list. The signing secret is on the endpoint's detail page under **Signing secret**. |
154
- | Webhook URL not accepted (must be HTTPS) | Stripe requires HTTPS for webhook endpoints. If Taskmaster is only on HTTP locally, use a tunnel tool like ngrok during development, or configure a TLS certificate on your Taskmaster host. |
155
- | Payments confirmed in Stripe but Taskmaster isn't notified | Check the webhook endpoint's delivery log in Stripe (Developers → Webhooks → click endpoint → Recent deliveries). If deliveries are failing, the URL may be unreachable from the internet. |
156
- | "I already had a webhook set up" | You can use the existing endpoint if it already points to `/webhook/stripe` on your host. Just copy the signing secret from its detail page. |
97
+ | Can't find Developers menu | In the left sidebar, near the bottom. May need to scroll or expand a collapsed sidebar. |
98
+ | Secret key hidden / "Restricted key" shown | Looking at a restricted key. Click **API keys** secret key is under **Standard keys**. |
99
+ | Can't find signing secret | Click on the endpoint row in the Webhooks list signing secret is on the endpoint detail page. |
100
+ | Webhook URL not accepted (must be HTTPS) | Stripe requires HTTPS. For local-only Taskmaster, Tailscale Funnel provides a public HTTPS URL. |
101
+ | Payments confirmed but Taskmaster not notified | Check delivery log: Developers → Webhooks → click endpoint → Event deliveries. |
102
+ | Already had a webhook set up | Use the existing endpoint if it already points to `/webhook/stripe`. Just copy the signing secret. |