Theme
Datasources API
External data injected into Story content at render time. The editor drops a $ds marker into a field; the CDN resolves the marker to live data from an HTTP source (or pushed via webhook) before serving the Story.
Two driver types
| Driver | Direction | Use case |
|---|---|---|
http_json | Peacock pulls, on cron schedule | Public APIs, news feeds, Shopify product lists |
webhook_push | Source pushes, signed with HMAC | Inventory systems, ERPs that emit events on change |
CRUD
http
GET /v1/spaces/{slug}/datasources
POST /v1/spaces/{slug}/datasources
PATCH /v1/spaces/{slug}/datasources/{key}
DELETE /v1/spaces/{slug}/datasources/{key}POST body for http_json:
json
{
"key": "current-weather",
"label": "Vienna weather",
"type": "http_json",
"config": {
"url": "https://wttr.in/Vienna?format=j1",
"json_path": "$.current_condition[0]",
"cron": "*/15 * * * *",
"headers": { "User-Agent": "PeacockCMS/0.1" }
}
}POST body for webhook_push:
json
{
"key": "shopify-products",
"label": "Shopify product catalog",
"type": "webhook_push",
"config": {
"secret_hash": "auto",
"allow_unsigned": false,
"schema": {
"id": "string",
"title": "string",
"price_cents": "integer"
}
}
}The response includes the webhook_secret (only shown once) for configuring HMAC signing on the source side.
Webhook ingest endpoint
http
POST /v1/spaces/{slug}/datasources/{key}/webhook
Content-Type: application/json
X-Peacock-Signature: sha256=<hex hmac>
X-Peacock-Timestamp: <unix-seconds>Body: a single entry or array of entries:
json
[
{ "id": "sku-1", "title": "T-Shirt", "price_cents": 1990 },
{ "id": "sku-2", "title": "Hoodie", "price_cents": 5490 }
]Signature: hex(hmac_sha256(secret, "${ts}.${raw_body}")). Timestamps older than 5 minutes are rejected to prevent replay.
allow_unsigned: true is the dev-only escape hatch — disabled by default; if enabled, signature verification is skipped (use only for local testing).
Manual sync
http
POST /v1/spaces/{slug}/datasources/{key}/syncTriggers an immediate pull for http_json driver (ignored for webhook_push). Returns the resulting entry count + first-row sample.
Entries
http
GET /v1/spaces/{slug}/datasources/{key}/entries?page=1&per_page=50The data the CDN sees when resolving $ds. Searchable, paginated, useful for the admin's "what does my data actually look like" panel.
$ds markers in Story content
In any text field, write $ds:current-weather.temp_C (or any JSONPath-like accessor). On CDN read:
- The controller collects all markers in the published content.
- For each unique
key, loads the latest entry / entries. - Substitutes the resolved values inline.
- Sets
meta.datasource_refs = { "current-weather": 1 }so the frontend can know what was rendered.
If a datasource is offline or returns no data, the marker is replaced with an empty string — the Story doesn't break.
Scheduling (cron)
http_json datasources fire on the cron schedule in their config.cron. A scheduler tick (peacock:sync-due-datasources, registered to run every minute) walks the table, dispatches SyncDatasourceJob for any due rows.
The host-level cron entry is required:
cron
* * * * * docker exec peacock-api php artisan schedule:run >> /var/log/peacock-schedule.log 2>&1See also
/api/plugins— for transforming data after fetch/api/webhooks— for cache-invalidation hooks/guide/personalisation— for audience-based variants (a different axis than data freshness)