{
"id": 123,
"occurred_at": "2023-11-07T05:31:56Z",
"data": {
"posts": [
{
"id": 123,
"external_id": "<string>",
"platform": "<string>",
"url": "<string>",
"message": "<string>",
"external_created_at": "2023-11-07T05:31:56Z",
"poster": {
"display_name": "<string>",
"name": "<string>",
"url": "<string>",
"image": "<string>",
"platform_id": "<string>"
},
"media": [
{
"url": "<string>",
"preview_image_url": "<string>",
"width": 123,
"height": 123,
"alt_text": "<string>",
"platform_id": "<string>"
}
],
"like_count": 123,
"comment_count": 123,
"share_count": 123,
"view_count": 123,
"quote_count": 123,
"bookmark_count": 123,
"impression_count": 123,
"tagged_users": "<string>",
"ai_moderation_reasoning": "<string>",
"pinned": true,
"referenced_post": {
"poster": {
"display_name": "<string>",
"name": "<string>",
"url": "<string>",
"image": "<string>",
"platform_id": "<string>"
},
"message": "<string>",
"url": "<string>",
"platform_id": "<string>",
"post_created_at": "2023-11-07T05:31:56Z",
"media": [
{
"url": "<string>",
"preview_image_url": "<string>",
"width": 123,
"height": 123,
"alt_text": "<string>",
"platform_id": "<string>"
}
]
},
"reshared_by": {
"display_name": "<string>",
"name": "<string>",
"url": "<string>",
"image": "<string>",
"platform_id": "<string>"
},
"created_at": "2023-11-07T05:31:56Z"
}
],
"feed": {
"id": 123,
"name": "<string>"
},
"source": {
"id": 123,
"platform": "<string>"
}
}
}New posts were ingested by a sync
{
"id": 123,
"occurred_at": "2023-11-07T05:31:56Z",
"data": {
"posts": [
{
"id": 123,
"external_id": "<string>",
"platform": "<string>",
"url": "<string>",
"message": "<string>",
"external_created_at": "2023-11-07T05:31:56Z",
"poster": {
"display_name": "<string>",
"name": "<string>",
"url": "<string>",
"image": "<string>",
"platform_id": "<string>"
},
"media": [
{
"url": "<string>",
"preview_image_url": "<string>",
"width": 123,
"height": 123,
"alt_text": "<string>",
"platform_id": "<string>"
}
],
"like_count": 123,
"comment_count": 123,
"share_count": 123,
"view_count": 123,
"quote_count": 123,
"bookmark_count": 123,
"impression_count": 123,
"tagged_users": "<string>",
"ai_moderation_reasoning": "<string>",
"pinned": true,
"referenced_post": {
"poster": {
"display_name": "<string>",
"name": "<string>",
"url": "<string>",
"image": "<string>",
"platform_id": "<string>"
},
"message": "<string>",
"url": "<string>",
"platform_id": "<string>",
"post_created_at": "2023-11-07T05:31:56Z",
"media": [
{
"url": "<string>",
"preview_image_url": "<string>",
"width": 123,
"height": 123,
"alt_text": "<string>",
"platform_id": "<string>"
}
]
},
"reshared_by": {
"display_name": "<string>",
"name": "<string>",
"url": "<string>",
"image": "<string>",
"platform_id": "<string>"
},
"created_at": "2023-11-07T05:31:56Z"
}
],
"feed": {
"id": 123,
"name": "<string>"
},
"source": {
"id": 123,
"platform": "<string>"
}
}
}Authorizations
API key prefixed with jcr_. To obtain a key programmatically
(no dashboard required), see POST /v1/authorize.
Body
The JSON body POSTed to your endpoint.
Test deliveries from POST /webhooks/{id}/test use this same envelope,
but their data is a fixed {test: true, …} object rather than real
event data, and their event is whichever event you subscribed to first.
Branch on data.test before treating data as an event payload.
Deliveries are at-least-once. Dedup on id, which is stable across
every attempt and every replay of an event. Do not use the
X-Juicer-Delivery-Id header for this — it is regenerated on every
attempt, so a receiver that processed a delivery but answered too slowly
will see the retry under a different value. The same id also arrives
at every webhook you have subscribed to that event, once per endpoint.
Failed deliveries are retried automatically. A non-2xx response, a
timeout, or a connection error fails that attempt, and the next one is
scheduled 30s, then 2m, 8m, 32m and 2h later — six attempts in all,
spread over roughly 2h45m. Answer within 10 seconds — 30 for
post.created, whose body carries a whole sync batch — and do your
processing after responding. Every attempt is its own row in
GET /webhooks/{webhook_id}/deliveries, carrying its own http_status
and error_message, and attempt_number counts attempts within one
delivery: a new pending row means the next attempt is queued, and
failed on attempt 6 means Juicer has given up.
Once the retries are exhausted, replay the event yourself with
POST /webhooks/{webhook_id}/deliveries/{id}/redeliver. That queues a
fresh delivery of the same event under the same id, with its own full
run of automatic retries.
Only a delivery that exhausts its retries counts against the webhook's
health, so a single flaky endpoint burns one failure, not six. After 10
consecutive failed deliveries the webhook's status flips to disabled
and deliveries stop — including redeliveries, which are accepted but
never sent — until you re-enable it with PATCH /webhooks/{id}
(status: active). Re-enabling resets consecutive_failures to 0, so a
recovered endpoint starts over with the full budget. A successful
delivery resets it too.
Verify authenticity by computing HMAC-SHA256 over the raw request body
with your webhook's secret and comparing to X-Juicer-Signature
(formatted sha256=<hex>). Parsing and re-serializing the JSON before
hashing will not match.
The webhook event's id, and your idempotency key. Retrievable via
GET /webhook_events/{id}.
Also sent in the X-Juicer-Event header.
post.created, social_account.expiring, social_account.expired When the event was recorded, in UTC.
Sent for post.created. One event covers a whole sync, so posts holds
every post that sync newly created — a source's first sync commonly
carries tens of posts. Posts the sync merely updated (refreshed
engagement counts, edited captions) are not included.
Posts held for moderation are included; check moderation_status if you
only want ones visible in the feed.
moderation_status is the state at ingestion, not a settled verdict.
A post queued for AI moderation is delivered as moderated and is
commonly approved a few seconds later; a human can approve or reject at
any time after that. Neither sends another event — post.created is the
only post event Juicer emits. A subscriber that keeps only public
posts from this payload therefore drops posts that did become public, so
re-read them through GET /feeds/{feed_id}/posts for the settled status.
- Option 1
- Option 2
- Option 3
Show child attributes
Show child attributes
Response
Return any 2xx to acknowledge. Any other response, a timeout, or a
connection error fails the attempt and is retried up to 5 times with
growing backoff; see WebhookDeliveryBody for the full contract.