We build custom applications 5x faster and cheaper 🚀
Book a Free Consultation
Stuck on an error? Book a 30-minute call with an engineer and get a direct fix + next steps. No pressure, no commitment.
Direct answer: Build a small stateless skill that performs authenticated HTTP calls to Bear Notes’ API (or to whatever Bear exposes) and install it through ClawHub; use OAuth (preferred) or API keys to obtain scoped tokens, store those secrets in ClawHub or an external secret store, and implement an explicit OAuth callback or webhook receiver on an external HTTPS endpoint that the skill calls when invoked by the OpenClaw agent. Keep the agent runtime stateless (calls the Bear API at runtime), put long-term storage or background sync outside the agent, validate webhooks and rate limits, and debug by checking ClawHub/agent logs, inspecting Bear API responses, and verifying tokens/scopes. Replace the placeholder endpoints and scopes in the examples below with the real Bear Notes endpoints and scopes you get from the Bear/third‑party developer docs.
// Node.js + express example: OAuth code exchange and token storage
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.get('/oauth/callback', async (req, res) => {
const code = req.query.code;
if (!code) return res.status(400).send('Missing code');
<b>//</b> Exchange code for tokens at Bear's token endpoint (replace URL)
const tokenResp = await fetch('https://api.bearnote.example/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
client_id: process.env.BEAR_CLIENT_ID,
client_secret: process.env.BEAR_CLIENT_SECRET,
redirect_uri: process.env.BEAR_REDIRECT_URI
})
});
if (!tokenResp.ok) {
const text = await tokenResp.text();
return res.status(500).send('Token exchange failed: ' + text);
}
const tokenJson = await tokenResp.json();
<b>//</b> persist tokens to a secure store (DB/Vault). Example: save tokenJson.access_token and tokenJson.refresh_token
await saveTokensToSecretStore(req.query.state /*owner*/, tokenJson);
return res.send('Authorization complete');
});
app.listen(3000);
// Skill execution example: fetch notes using access token (replace API URL)
const fetch = require('node-fetch');
async function fetchNotesForUser(userId) {
<b>//</b> Get an access token from secure store or token service
const accessToken = await getAccessTokenForUser(userId);
const resp = await fetch('https://api.bearnote.example/v1/notes', {
headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/json' }
});
if (resp.status === 401) {
<b>//</b> Token might be expired. Either refresh here or signal caller to refresh.
throw new Error('Unauthorized: token expired or invalid');
}
if (!resp.ok) {
const body = await resp.text();
throw new Error(`Bear API error ${resp.status}: ${body}`);
}
return resp.json();
}
// Webhook verification example (HMAC SHA256). Replace header name and secret usage according to Bear docs.
const crypto = require('crypto');
app.post('/webhook', express.json(), (req, res) => {
const signature = req.headers['x-bear-signature']; <b>//</b> placeholder header name
const secret = process.env.BEAR_WEBHOOK_SECRET;
const payload = JSON.stringify(req.body);
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(payload).digest('hex');
if (!signature || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
<b>//</b> enqueue or handle the event
handleWebhookEvent(req.body);
res.status(202).send('Accepted');
});
Speak one‑on‑one with a senior engineer about your no‑code app, migration goals, and budget. In just half an hour you’ll leave with clear, actionable next steps—no strings attached.
1
Direct answer: A 401 on an OAuth refresh request means the provider rejected the refresh—usually because the refresh token was revoked/expired, the client_id/secret (or Authorization header) is wrong, the token endpoint is incorrect, or the skill’s stored env vars in ClawHub are stale; fix by verifying credentials, token endpoint, headers, logs, and if needed force a user re‑authorization to obtain a new refresh token.
// curl example
curl -X POST https://auth.example.com/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "CLIENT_ID:CLIENT_SECRET" \
-d "grant_type=refresh_token&refresh_token=REFRESH_TOKEN"
2
Direct answer: X-OpenClaw-Signature mismatches almost always come from using a parsed JSON body instead of the raw HTTP payload, using the wrong secret or hash encoding (hex vs base64), or a different HMAC algorithm/timestamp canonicalization. Confirm Bear Notes’ signing scheme, fetch the raw request bytes in OpenClaw, compute the HMAC with the exact secret and encoding, and compare using a constant-time check.
// express with raw-body middleware
const crypto = require('crypto');
// rawBody is the raw Buffer from the HTTP request
const sig = crypto.createHmac('sha256', process.env.BEAR_SECRET)
.update(rawBody) // use raw bytes
.digest('hex'); // or 'base64' per docs
// constant-time compare
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(req.headers['x-openclaw-signature']))) {
// reject
}
3
Cause: an ETag mismatch means Bear (server) changed the note after your agent read it; the PATCH with If-Match is rejected (412). Fix: before writing, fetch the latest note/ETag, merge or prompt the user, then retry the update with the fresh ETag; log and surface conflicts from the skill.
// fetch latest note
const res = await fetch(noteUrl, { headers: { Authorization: `Bearer ${token}` } });
// patch with If-Match
const patch = await fetch(noteUrl, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${token}`,
'If-Match': currentEtag,
'Content-Type': 'application/json'
},
body: JSON.stringify({ content: newContent })
});
if (patch.status === 412) {
// re-fetch, merge, retry or surface conflict
}
4
415 Unsupported Media Type means the OpenClaw endpoint is rejecting the Content-Type you send. Inline Base64 inside JSON often causes this; the fix is to send the image as a real file upload (multipart/form-data) or match the API’s accepted content-type exactly.
//<b>//</b> build multipart from base64
const form = new (require('form-data'))()
form.append('file', Buffer.from(base64, 'base64'), { filename:'img.png', contentType:'image/png' })
fetch(url, { method:'POST', headers:{ Authorization:`Bearer ${TOKEN}`, ...form.getHeaders() }, body: form })
This prompt helps an AI assistant understand your setup and guide you through the fix step by step, without assuming technical knowledge.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â