Skip to content

Callbacks

A callback is a per-request, outbound notification you attach to an order so CloudPress POSTs to your URL the moment the order's async work finishes — instead of you polling the task. You supply the callback when you place the order; CloudPress fires it once that order's task reaches a terminal state (OK, FAILED, or CANCELLED).

Callback vs. billing webhook

A callback is per request — it belongs to the single order you attach it to, and fires once. A billing webhook is plan-level — configured by an admin on the billing plan, it fires on every service lifecycle event under that plan. Different mechanisms; see the comparison below.


How it works

  1. You place an order with a callback object (POST /api/orders, POST /api/orders/domain, or POST /api/cpanel_accounts).
  2. CloudPress accepts the order (202 Accepted) and provisions asynchronously as a task.
  3. When the task completes (or fails), CloudPress sends an HTTP POST to your callback url.
  4. If you don't register a callback, poll GET /api/tasks/{task-id} instead.

Delivery is retried but not guaranteed. A delivery that completes with a non-2xx status is retried, so your receiver must be idempotent — but a delivery that never completes at all (connection refused, timeout) is not retried and is lost. Read Delivery & retries before you build on callbacks as your only signal.


Registering a callback

Add a callback object to the order body. Three endpoints accept it:

  • POST /api/orders — site orders
  • POST /api/orders/domain — domain registration / transfer orders
  • POST /api/cpanel_accounts — cPanel hosting account orders
Params
  • callback: Object (optional) | Fired when the order's async task completes.
    • url: String (required) | Fully-qualified HTTPS URL that receives the POST.
    • authorization: String (optional) | Sent verbatim as the Authorization header on the callback. Supply the full value — e.g. Bearer 12345, Token 12345, or a raw token.
curl -X POST https://my.cloudpress.com/api/orders \
  -H "Authorization: Bearer $CLOUDPRESS_TOKEN" \
  -H "X-Auth-Account: $ACCOUNT_ID" \
  -H "Content-Type: application/json" \
  -d '{
        "order": { "plan": "basic", "term": "monthly" },
        "callback": {
          "url": "https://your-app.example.com/cloudpress/callback",
          "authorization": "Bearer your-shared-secret"
        }
      }'

What CloudPress sends

When the task finishes, CloudPress sends a POST to your url:

  • Headers: Authorization: <your authorization value> (verbatim; omitted if you didn't supply one) and Accept: application/json.
  • Body: JSON.
{
  "timestamp": 1727303593,
  "success": true,
  "data": "Site provisioned. Primary domain: example.com"
}
Body Params
  • timestamp: Integer | Unix epoch (seconds) at which this delivery was queued. Stable across the retries of one delivery, but not a unique event key — see Delivery & retries.
  • success: Boolean | true when the task ended OK; false when it ended FAILED or CANCELLED.
  • data: String | The task's data field — the same free-form result text returned by GET /api/tasks/{task-id}.

success: false does not always mean the task failed

A cancelled task fires the callback too, and reports "success": false — byte-for-byte indistinguishable from a genuine failure. The body carries no status field to tell them apart. If the distinction matters to you, read the task with GET /api/tasks/{task-id}, where CANCELLED and FAILED are separate values.


Delivery & retries

Callbacks share CloudPress's outbound delivery layer (the same one used by billing webhooks).

A timeout or a refused connection is never retried

Only deliveries that complete as an HTTP response with a non-2xx status enter the retry schedule. If the request never completes at all — connection refused, DNS or TLS failure, or the 30-second timeout elapsing — the delivery is attempted once and then abandoned permanently. Nothing is re-queued and nothing is re-sent, so a receiver that is unreachable for even a moment loses that callback for good.

Treat callbacks as best-effort for exactly this reason. For anything you cannot afford to miss, keep a periodic poll of GET /api/tasks/{task-id} as the backstop rather than relying on the callback alone.

  • Timeout: 30 seconds per attempt.
  • Success: any HTTP 2xx. A completed non-2xx response is a failed delivery and is retried. A connection failure or timeout is not — see the warning above.
  • Retries: the first retry is sent 5 minutes after the original attempt, and every 15 minutes after that.
  • Giving up: retries stop 4 hours after the first attempt.
  • Idempotency: retries mean the same callback may arrive more than once, so your receiver must be idempotent. timestamp is not a safe de-duplication key — it is stable across the retries of a single delivery, but a task that is finalized more than once (the provisioning job and an inbound POST /api/webhooks/task/{task-id} each trigger a delivery) produces a fresh timestamp for the same result. The body carries no task id either, so correlate on something you control: put an order or task reference in the callback url you register, and make the handler safe to run twice.

Everything below is the inbound direction

The callback described above is outbound — CloudPress POSTing to your URL. The remaining endpoints on this page are the mirror image: /api/webhooks/* routes that CloudPress's own provisioning infrastructure calls into the API. They are documented here because posting to them is what triggers the outbound callback above, but they are not part of an integrator's normal flow — they authenticate with a back-end system API key and are blocked for OAuth tokens. Don't confuse the two directions.

Testing reachability

GET /api/webhooks/task

Returns the source IP CloudPress sees for your request, so you can confirm network reachability / allow-listing before relying on a callback.

Returned Params
  • ip_address: String

An identical probe exists on the cache-webhook path — GET /api/webhooks/cdn_cache.


Reporting a task result

POST /api/webhooks/task/{task-id}

This is the endpoint that marks a task complete inside CloudPress — it is how provisioning infrastructure reports a result back, and posting that result is what triggers your registered outbound callback. It updates the task's data, sets its status (OK / FAILED), and then fires the callback.

Account Scope Required (X-Auth-Account). Not available via OAuth — this endpoint authenticates with a back-end API key (it is normally called by CloudPress infrastructure, not by integrators).

Always returns 200, even for an unknown task ID. Idempotent: a repeat post with the same data + success is de-duplicated by digest and is a no-op.

Params
  • data: Any (optional) | Appended to the task's data.
  • success: Boolean (required) | true → task OK; false → task FAILED.

Requesting a CDN cache purge

POST /api/webhooks/cdn_cache

The inbound endpoint a site's own hosting container uses to ask CloudPress to purge that site's CDN cache — for example when a WordPress caching plugin flushes the page cache after a publish. CloudPress resolves the site, opens a task named site.cache.cdn_purge, and performs the purge asynchronously.

Not available via OAuth. This endpoint authenticates with a back-end, IP-allow-listed system API key — it is called by CloudPress infrastructure, not by integrators. An OAuth token is rejected with 403 insufficient_scope, so a third-party app can never purge a customer's CDN on behalf of infrastructure.

Params
  • cs_project_id: Integer (required) | The site's internal project id. Must be a positive integer.
  • mode: String (required) | all to purge the whole pull zone, or paths to purge specific paths.
  • paths: Array<String> (required when mode is paths) | Up to 100 entries. A path ending in / or containing * is a prefix purge; anything else purges that exact object.
{ "cs_project_id": 4821, "mode": "paths", "paths": ["/", "/blog/*"] }
Returned Params
  • status: String | Always "accepted".
  • task_id: Integer | The purge task to poll — present when a purge was enqueued.
  • noop: Boolean | true when there was nothing to purge; task_id is then absent.

202 Accepted means accepted, not "purge confirmed" — the purge runs out of band and retries internally on transient CDN errors. An unknown site, or a site with no active CDN, is likewise an accepted no-op ({"status":"accepted","noop":true}) rather than an error, so the caller never has to tell "site gone" apart from "purged".

Because the caller is infrastructure rather than a person, the resulting task is attributed to System in the site's task history.

Errors
  • 400 invalid_cs_project_id | cs_project_id missing, or not a positive integer.
  • 400 invalid_mode | mode is neither all nor paths.
  • 400 missing_paths | mode is paths but no usable path was supplied.
  • 400 too_many_paths | more than 100 entries in paths.
  • 401 | authentication or IP allow-list failure.
  • 403 insufficient_scope | an OAuth token was used.

To purge a site's CDN cache as an integrator, use the ordinary per-site endpoints instead — see Purge the Cache and Purge a Cache Layer.

Testing cache-webhook reachability

GET /api/webhooks/cdn_cache

The same connectivity probe as GET /api/webhooks/task, on the cache-webhook path.

Returned Params
  • ip_address: String

Callbacks vs. billing webhooks

Callback Billing webhook
Configured on Per request, in the callback object on an order The billing plan (admin UI)
Scope The single task / order it was attached to Site, cPanel-account & domain lifecycle events under that plan
Fires Once, when that task reaches OK / FAILED / CANCELLED On every created / resized / registered / renewed / … event
Body { "timestamp", "success", "data" } (the task result) { "action", "site" }, { "action", "cpanel_account_link" } or { "action", "domain" }

See Billing Webhooks for the plan-level mechanism.