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.
To integrate Upload Post with OpenClaw you run a small external web service that handles uploads and webhooks, register and configure a Claw skill in ClawHub that calls that service via authenticated REST requests, store credentials (OAuth client or API keys) securely outside the agent runtime, validate incoming webhooks (HMAC or signature) in your external service, and keep any stateful or long-running work (file storage, retries, queues) outside the agent so the skill remains a thin, authenticated orchestrator that invokes your Upload Post API.
const express = require('express');
const fetch = require('node-fetch');
const crypto = require('crypto');
const FormData = require('form-data');
const app = express();
app.use(express.json());
const UPLOAD_POST_API = process.env.UPLOAD_POST_API;
const UPLOAD_POST_API_KEY = process.env.UPLOAD_POST_API_KEY;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
function verifySignature(body, signature) {
<b>//</b> compute HMAC-SHA256 using WEBHOOK_SECRET and compare to signature header
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
hmac.update(JSON.stringify(body));
const expected = `sha256=${hmac.digest('hex')}`;
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
app.post('/webhook', async (req, res) => {
<b>//</b> X-Signature header name is vendor-specific; adapt as needed
const signature = req.get('X-Signature') || '';
if (!verifySignature(req.body, signature)) {
return res.status(401).send('invalid signature');
}
<b>//</b> Example webhook contains a file_url the provider gave us
const { file_url, metadata } = req.body;
<b>//</b> Forward to Upload Post API (server-to-server call)
try {
const form = new FormData();
form.append('file_url', file_url);
form.append('metadata', JSON.stringify(metadata));
const r = await fetch(`${UPLOAD_POST_API}/posts`, {
method: 'POST',
headers: {
Authorization: `Bearer ${UPLOAD_POST_API_KEY}`,
// form.getHeaders() will include multipart content-type
...form.getHeaders()
},
body: form
});
if (!r.ok) {
const bodyText = await r.text();
console.error('Upload Post error', r.status, bodyText);
return res.status(502).send('upload failed');
}
const body = await r.json();
return res.status(200).json({ ok: true, result: body });
} catch (err) {
console.error('forward error', err);
return res.status(500).send('server error');
}
});
app.listen(3000);
const fetch = require('node-fetch');
async function startUpload(postData) {
<b>//</b> EXTERNAL_UPLOAD_URL is configured in ClawHub as an env var
const externalUrl = process.env.EXTERNAL_UPLOAD_URL;
const key = process.env.SKILL_INVOKE_KEY; <b>//</b> short-lived or static secret to authenticate the skill to your service
const r = await fetch(externalUrl + '/start-upload', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${key}`
},
body: JSON.stringify(postData)
});
if (!r.ok) {
const errorText = await r.text();
throw new Error(`upload start failed: ${r.status} ${errorText}`);
}
return r.json();
}
<b>//</b> Upload Post API expects an API key in Authorization header
curl -X POST 'https://api.upload-post.example/v1/posts' \
-H "Authorization: Bearer ${UPLOAD_POST_API_KEY}" \
-F "file_url=https://example.com/myfile.jpg" \
-F "metadata={\"title\":\"My post\"}"
<b>//</b> Compute HMAC externally or let the provider send it; here we post to our receiver
curl -X POST 'https://your.receiver.example/webhook' \
-H "Content-Type: application/json" \
-H "X-Signature: sha256=..." \
-d '{"file_url":"https://...","metadata":{"title":"x"}}'
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 means the skill isn’t sending the API key the remote service expects (wrong env name, header type, or not injected). Put the key in claw-config.yaml under the skill’s env, verify the runtime injects that env var, use the exact header the API expects (Authorization: Bearer or X-API-Key), and confirm the key is valid and permissioned.
skills:
my-skill:
env:
OPENCLAW_API_KEY: "sk_live_XXX"
curl -i -H "Authorization: Bearer sk_live_XXX" https://api.service/endpoint
// ensure env is injected in runtime
const apiKey = process.env.OPENCLAW_API_KEY; // must match claw-config key
fetch(url,{headers:{'Authorization':`Bearer ${apiKey}`}})
2
Multipart/form-data boundary errors happen when the HTTP Content-Type boundary doesn't match the body or is malformed—commonly because the client manually sets Content-Type (which omits the auto-generated boundary), the body was stringified, or a proxy rewrote headers. Fix by letting your HTTP lib set the header, ensure binary parts aren’t JSON/stringified, and confirm the final boundary (--boundary--) is present.
Steps to resolve:
// Node fetch example: allow FormData to set headers
const form = new FormData();
form.append('file', fs.createReadStream('img.png')); // stream, not string
fetch(url, { method: 'POST', body: form }); // do NOT set 'Content-Type'
3
Direct answer: Use resumable/chunked uploads by delegating storage to an external service (S3/GCS/tus), having your skill issue signed (presigned) URLs or manage multipart sessions, and keep upload state outside the agent runtime (DB or queue). Validate checksums, use idempotency tokens, and have the skill finalize/verify the upload.
Core pieces to build in OpenClaw skills and runtime:
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3 = new S3Client({ region: process.env.AWS_REGION });
// <b>//</b> generate presigned PUT URL
export async function presign(req,res){
const cmd = new PutObjectCommand({Bucket:process.env.BUCKET, Key:req.query.key});
const url = await getSignedUrl(s3, cmd, { expiresIn: 3600 });
res.json({url});
}
4
You should transform the upload response metadata into the OpenClaw Post schema before calling the platform API: extract title, body, author, tags, visibility and attachments from the upload result, normalize types, validate required fields, then call post.upload or pass the object to PostTransformer which returns the validated Post payload.
function PostTransformer(upload) {
return {
title: upload.meta.title || upload.filename,
body: upload.meta.description || "",
authorId: upload.userId,
tags: Array.isArray(upload.meta.tags) ? upload.meta.tags : [],
attachments: [{ url: upload.url, mimeType: upload.mime }],
visibility: upload.meta.visibility || "private"
};
}
// const post = PostTransformer(uploadResponse)
// await post.upload(post)
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.Â