Skip to content
Clevera Help Center

Set up webhooks for agent events

Get a signed POST on your own https endpoint when an agent's job finishes or fails, an agent waits for approval, or a doc site publishes. Events, payloads, signature verification, retries and auto-disable.

Updated 8 min read

A webhook sends a signed POST to your own endpoint the moment something happens, such as an agent's job finishing or an agent waiting for approval, so your systems don't have to poll. This article covers adding an endpoint, the events, the payload, verifying the signature and what happens when deliveries fail.

Before you start

  • Webhooks are on the Pro, Business and Custom plans ("Webhooks are on Pro and up.").
  • Only the workspace owner and admins manage webhooks. Other members see "Only owners and admins manage webhooks".
  • You need a public https address that answers with a 2xx status within 10 seconds.
  • A workspace can have up to 10 webhooks.

Add a webhook

  1. Open the Webhooks tab

    Open the workspace menu, choose AI agents & API, then open Webhooks. Click New webhook.

    The Webhooks tab with one endpoint at example.com, the events it receives, and when it last sent.
    Each webhook shows its events, when it last sent and the end of its secret.
  2. Enter the endpoint

    • Endpoint URL: "A public HTTPS address." For example https://example.com/clevera/webhooks.
    • Description: optional, up to 200 characters, for example "Slack relay".
  3. Choose the events

    Tick the events to receive, or All events, including new ones. Events marked Soon are accepted but "Nothing sends this event yet."

    The New webhook dialog: endpoint URL, description and the events to send, with events not sent yet marked Soon.
    Events marked Soon are accepted but not sent yet.
  4. Create it and store the secret

    Click Create webhook. The Signing secret (it starts with whsec_) shows once: "Copy it now: you won’t see it again. Use it to check the Clevera-Signature header." Store it like a password, then click I’ve stored it.

  5. Send a test event

    Open the webhook's ⋯ menu and choose Send test event. Clevera sends a signed ping event once (no retries). "Test event sent" means it left Clevera; check Deliveries for your endpoint's answer.

Events

Event Sent when
job.started A job an agent started begins running.
job.needs_input A job waits for a person, for example an approval. Includes the link and when the request expires.
job.succeeded The job finished.
job.failed The job failed. Includes the error code.
job.canceled The job was canceled, or its approval was denied or expired.
approval.requested An agent asked for approval on Agent requests.
approval.decided Someone approved or denied the request, or it expired.
site.published A doc site was published.
site.build_failed A doc site publish failed.
ping You clicked Send test event. Needs no subscription.

These events are listed as Soon and not sent yet: job.progress, video.ready, article.ready, research.completed, insights.weekly.

Job events cover jobs that agents start (through an API token or a connected app), not videos people start in the app.

What Clevera sends

Every delivery is a POST with a JSON body and these headers:

Header Value
Content-Type application/json
User-Agent Clevera-Webhooks/1.0
Clevera-Event The event name, like job.succeeded
Clevera-Delivery The delivery's unique id. A redelivery repeats the same id, so use it to ignore duplicates.
Clevera-Signature t=<unix time>,v1=<signature>

Payloads carry ids, links, statuses and numbers, never the content itself. To read a finished video or article, call the API with a token.

job.succeeded
{
  "id": "6f1c2d3e-4b5a-4c6d-8e9f-0a1b2c3d4e5f",
  "event": "job.succeeded",
  "created_at": "2026-10-20T14:03:11Z",
  "data": {
    "job": {
      "id": "job_8k2m4n6p8r0t2v4x",
      "kind": "video.create",
      "status": "succeeded",
      "error_code": null,
      "subject": { "type": "video", "id": "vid_example" },
      "credits_spent": 64
    },
    "needs_input": null,
    "workspace": { "id": "tm_example" }
  }
}
approval.requested
{
  "id": "0b9a8c7d-6e5f-4a3b-9c2d-1e0f9a8b7c6d",
  "event": "approval.requested",
  "created_at": "2026-10-20T14:01:02Z",
  "data": {
    "approval": {
      "id": "apr_example",
      "kind": "spend",
      "status": "pending",
      "credits_high": 80,
      "expires_at": "2026-10-21T14:01:02Z",
      "decided_at": null,
      "url": "https://app.clevera.ai/agents?tab=requests&team=tm_example"
    },
    "job": { "id": "job_8k2m4n6p8r0t2v4x", "kind": "video.create" },
    "workspace": { "id": "tm_example" }
  }
}

Verify the signature

Check every delivery before you trust it. The signature is an HMAC-SHA256, keyed with your signing secret, of the timestamp, a dot and the raw request body: <t>.<body>.

  1. Read t and v1 from the Clevera-Signature header.
  2. Compute HMAC-SHA256(secret, t + "." + rawBody) as hex.
  3. Compare it with v1 in constant time.
  4. Reject deliveries whose t is more than 5 minutes old, so a captured request can't be replayed.
verify.js
import crypto from 'node:crypto';

export function verifyClevera(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
  const t = Number(parts.t);
  if (!parts.v1 || !Number.isInteger(t)) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return expected.length === parts.v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Use the body exactly as received. Parsing and re-encoding the JSON first changes the bytes and the signature won't match.

Retries and failures

A delivery counts as delivered when your endpoint answers with a 2xx status. Anything else fails the attempt:

  • any other status, including redirects (3xx are not followed);
  • no answer within 10 seconds (5 seconds to connect);
  • an address that no longer resolves to the public internet.

Clevera retries a failed delivery 5 times, after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. Your endpoint's response body is never read.

When 15 deliveries in a row fail every attempt, the webhook is switched off: "Turned off after 15 failed deliveries in a row. Fix the endpoint, then turn it on." The owner and admins get a "Webhook to host switched off" notification: "Its deliveries kept failing. Fix the endpoint, then switch it back on." Events that happen while it's off are not sent. Test events don't count towards this.

Manage a webhook

Click Deliveries on a webhook to see its log, or open its ⋯ menu for the rest:

Option What it does
Edit Change the URL, events or description.
Deliveries The latest 100 deliveries with their status (Delivered, Failed, Sending), Attempts and the HTTP code. Redeliver sends one again. Deliveries are kept for 30 days.
Send test event Sends a signed ping.
Regenerate secret "Deliveries are signed with the new secret right away. Update your endpoint to check it." There's no overlap, so update your endpoint straight after.
Turn off / Turn on Pause and resume deliveries.
Delete "We stop sending events to it and drop its delivery log. This can’t be undone."

If the workspace leaves Pro

Webhooks stay listed and can still be edited and deleted, but nothing is sent. Deliveries waiting for a retry end as failed with plan_locked, and Send test event and Redeliver say "Webhooks are not on your plan." Upgrading back to Pro starts sending again; nothing is deleted.

Troubleshooting

Message Cause Fix
"Webhooks are not on your plan." The workspace isn't on Pro, Business or Custom. Upgrade to Pro.
"Use an https:// address." The URL starts with http://. Serve the endpoint over HTTPS.
"Use a host name, not an IP address." The URL uses an IP address. Use a domain name.
"This address isn’t reachable on the public internet." The host resolves to a private or local address. Use a public endpoint, or a relay.
"Remove the user name and password from the address." The URL has user:password@. Remove them; check the signature instead.
"Enter a valid web address." The URL isn't valid. Check for typos.
"Choose at least one event." No event ticked. Tick at least one event.
"This workspace already has 10 webhooks." The workspace limit. Delete one you don't use.
"Switch the webhook back on first." You tried to redeliver on a webhook that's off. Turn it on, then redeliver.
"This delivery is still being sent." The delivery is still retrying. Wait for it to finish.
Deliveries fail with an HTTP code Your endpoint answered with an error. Check your endpoint's logs and that it returns 2xx quickly; do slow work after answering.
Signatures never match The body was changed before checking, or the secret is old. Verify against the raw body; regenerate the secret if unsure.