ReeplHELP

Webhooks

Subscribe to Reepl events, verify the signature on every delivery, and understand retries, failures, and the source filter.

A webhook tells your system when something happens in Reepl, so you do not have to poll for it. Register a URL, pick the events you care about, and Reepl posts JSON to that URL whenever one fires.

Webhooks require a Pro plan or above. Registering one through the API needs a key with the webhooks:manage scope.

Register a subscription

Open Settings → Developers → Webhooks

Go to Settings → Developers and switch to the Webhooks tab.

Add your endpoint

Give Reepl the URL to POST to. It has to be reachable from the public internet — localhost will not work, so use a tunnel while developing.

Choose your events

Tick the events you want. Group by group is fine; subscribing to everything is rarely what you want, because you pay the verification cost on every delivery.

Save the signing secret

Reepl generates a secret starting with whsec_. You need it to verify deliveries. Store it the way you would store a password.

You can do the same over the API with POST /external/webhooks/subscriptions, and fire a test delivery with POST /external/webhooks/test.

Events

Twenty events, in eight groups.

Posts

EventFires when
post_publishedA post went live on a connected channel
post_scheduledA post was scheduled for later
post_updatedA scheduled or published post was edited
post_deletedA post was deleted
publish_failedA post failed to publish

Drafts

EventFires when
draft_createdA new draft was created
draft_updatedA draft was edited
draft_deletedA draft was deleted

Articles

EventFires when
article_publishedAn article was published to a destination
article_publish_failedAn article failed to publish to a destination
article_draft_createdReserved. You can subscribe to it, but nothing currently emits it.

Carousels

EventFires when
carousel_draft_createdA new carousel draft was created
carousel_draft_updatedA carousel draft was edited
carousel_draft_deletedA carousel draft was deleted

Engagement

EventFires when
comment_addedA comment was added on a post

Signals

EventFires when
signal_receivedA new signal was captured. Signals sourced from X are excluded.

AI agent

EventFires when
approval_requestedA post entered your workspace's review queue — queued by Andy, or submitted through the API

Contacts (deprecated)

EventFires when
contact_addedA contact was added
contact_updatedA contact was updated
contact_deletedA contact was removed

Contacts have been removed from the Reepl app. These three events still fire but are scheduled for removal — do not build anything new on them.

Only hearing about AI-generated activity

Four events — post_published, post_scheduled, draft_created and approval_requested — carry a source field saying what created them. Add a filter to your subscription to receive only those:

{ "filters": { "source": "andy" } }

Filters are matched against the top level of the event payload and apply to the whole subscription, not to individual events. Any event whose payload lacks a matching key is filtered out, so do not add a source filter to a subscription that also carries events without one.

Verifying a delivery

Every request carries four headers:

HeaderContents
X-Reepl-EventThe event type, e.g. post_published
X-Reepl-Webhook-IDUnique delivery id, del_… — use it to deduplicate
X-Reepl-Webhook-TimestampUnix seconds, as a string
X-Reepl-Webhook-Signaturev1= followed by the hex HMAC

The signature is an HMAC-SHA256 over the timestamp, a literal dot, and the raw request body, keyed with your whsec_ secret:

signature = HMAC_SHA256(secret, "{timestamp}.{raw_body}")
import crypto from "node:crypto";

function verify(rawBody, headers, secret) {
  const timestamp = headers["x-reepl-webhook-timestamp"];
  const received = headers["x-reepl-webhook-signature"];

  const expected =
    "v1=" +
    crypto
      .createHmac("sha256", secret)
      .update(`${timestamp}.${rawBody}`)
      .digest("hex");

  // Constant-time compare, so a timing attack cannot recover the signature.
  const a = Buffer.from(expected);
  const b = Buffer.from(received ?? "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Verify against the raw body, exactly as received. If your framework parses JSON before you get to it, re-serializing will change the bytes and every signature will fail. Reject anything that does not verify, and reject old timestamps to blunt replay attempts.

Retries and failures

Reepl waits up to 30 seconds for your endpoint to respond. Return a 2xx quickly and do the real work afterwards — a slow handler reads as a failure.

A delivery is retried when it times out, when the connection fails, or when your endpoint returns 429 or any 5xx. Other 4xx responses are treated as a permanent rejection and are not retried.

Retries back off at 1 minute, 5 minutes, 15 minutes, then 1 hour, for up to 5 attempts.

After 10 consecutive failures the subscription is disabled automatically and stops delivering until you re-enable it. If a webhook goes quiet, check whether it was switched off.

Deliveries can arrive more than once. Deduplicate on X-Reepl-Webhook-ID rather than assuming exactly-once.