Update a blog post (required)
Partial update — apply only the fields present in the body.
This is the rule most integrations get wrong, and the one that costs real
content. Castro’s Update Content action sends nothing but content; a
full Update Content & Metadata sends every field. If you replace the
record with whatever arrived, the first case wipes the title, categories,
author and SEO the user set.
// WRONG — replaces the row with whatever arrived.
await db.posts.replace(id, body);
// RIGHT — merge only the keys that are present.
for (const [k, v] of Object.entries(body)) {
if (v !== undefined) post[k] = v;
}
Authorizations
The connection key the user generated in Jorge Castro (Settings → Integration → Custom Website). Reject any request whose key doesn't match yours.
Unix time in milliseconds when the request was signed — Castro uses
Date.now(). Reject anything more than a few minutes old.
A seconds-based comparison makes every request look ~55,000 years in the future, and the freshness check then silently passes everything.
HMAC-SHA256("{timestamp}.{rawBody}", api_key), lowercase hex.
rawBody is the exact bytes of the request body — empty for GET
and DELETE, which therefore sign over "{timestamp}.". Hash the raw
bytes, never a re-serialized copy of the parsed JSON: whitespace and key
order differences will break the comparison for some payloads and not
others, which reads as an intermittent bug.
Compare in constant time (crypto.timingSafeEqual, hash_equals,
hmac.compare_digest).
Path Parameters
The id your server returned when the entity was created. Castro stores it verbatim and never rewrites it.
Body
The body of PUT /posts/{id} — a partial update. Any subset of the
fields may arrive, and only those fields change. Everything you are
not sent must keep its current value.
Three different Castro actions come through this one endpoint:
| Castro action | What you receive |
|---|---|
| Update Content & Metadata | Every field (same shape as create) |
| Update Content | { "content": "…", "source_id": "…" } |
| Change status | { "status": "draft" } |
Treat the second as a full replace and you erase the user's title,
categories, author and SEO. Nested objects follow the same rule — merge
seo, don't replace it.
"10 Best Running Shoes in 2026"
Post body as HTML. The H1 is already stripped — render title as your page heading.
"<p>Choosing the right running shoe...</p>"
Short summary / meta description text.
publish, draft Publish datetime, YYYY-MM-DD HH:MM:SS.
"2026-07-03 14:22:01"
Category NAMES. Create any that don't exist yet.
["Running", "Gear"]
["shoes", "running"]
Author display name.
"Jane Levy"
URL of the featured image, hosted by Castro.
"5 minutes"
Show child attributes
Show child attributes
Castro's internal content id — store it for idempotency.
"cnt_9f2ka83b"

