Run webhooks, inbound routes, and background jobs
Receive events in an app and hand slow work to a job. Begin with a working function and an installation with the permissions needed by that work. Use idempotent handlers because events and jobs can retry.
7. Wire a trigger
Section titled “7. Wire a trigger”init already created lifecycle hooks under src/app/lifecycle/ that call
on-install, on-update, on-uninstall, and on-rotate. Add the trigger
you actually need:
caraer apps add function catch-createdcaraer apps add webhook --topic record.candidate.created --function catch-createdcaraer apps add schedule heartbeat --function catch-created --cron "0 0 */12 * * *"caraer apps add inbound provider-push --function catch-created --auth SHARED_SECRETRecord webhooks are always object-scoped:
record.{object}.{created|updated|deleted|all}. There is no company-wide
record.created topic.
| Trigger | Use when |
|---|---|
Webhook (record.candidate.created, …) |
A record event on that object should run your code |
| Inbound | An external system POSTs to a public URL |
| Schedule | Cron (Spring 5–6 field expression) |
| Lifecycle | Install, settings save, uninstall, or token rotate |
| App bar | A button on a record, a trait tab, or a sidebar tool. See App bars in Caraer apps |
Inbound URL and params
Section titled “Inbound URL and params”There is no copyable inbound URL in the Caraer app today. After deploy, assemble
it from the API host, the app UUID (caraer.json / caraer apps select), the
route name in src/app/inbound/<name>.json, and the installing company UUID
(caraer company list):
POST {caraerApiBase}/v2/public/apps/{appUuid}/inbound/{routeName}?companyUuid={companyUuid}Content-Type: application/jsonX-Caraer-Inbound-Secret: <shared-secret>companyUuid can also be sent as X-Caraer-Company-Uuid instead of a query
param. For SHARED_SECRET routes, send the secret in X-Caraer-Inbound-Secret.
validate warns if sharedSecret is missing.
Pass data in the JSON body. The function receives:
{ "action": "app.inbound", "inboundRoute": "provider-push", "body": { "hello": "caraer" }}Query params other than companyUuid are not forwarded. Auth headers are used
only to verify the caller.
Locally, caraer apps local dev prints
POST http://127.0.0.1:8787/inbound/<routeName> and accepts the same JSON
body.
enqueue: true (the default) returns 202 and runs the function as a job.
Long work should enqueue and return quickly.
Enqueue slow work
Section titled “Enqueue slow work”Use POST {caraerApiBase}/v2/apps/{appUuid}/installation/jobs with a
functionName, payload, and optional delaySeconds. Read the current
installation runtime API for limits and job status operations.
Use state for cursors or deduplication markers; keep credentials in the
secrets vault.
Verify the whole path
Section titled “Verify the whole path”Test a local sample first, then send a real event to a sandbox deployment.
Confirm the queued job and its result. An accepted inbound request or READY
runtime alone does not prove the job or external provider completed.
Choose record webhook payload fields
Section titled “Choose record webhook payload fields”The published CLI accepts webhookFormat: MINIMAL and a comma-separated fields
selection on record webhooks. This reduces the main record’s properties; it does
not project related records. Use internal field names and verify the selected
app’s backend supports the requested format:
caraer webhooks formatsIn a function’s exported manifest, the webhook can include:
export const manifest = { webhooks: [{ topic: 'record.<setting:target_object>.created', webhookFormat: 'MINIMAL', fields: 'firstname,email', }],};MINIMAL excludes user/context metadata and recognised webpage fields.
Non-relation events omit event metadata. Relation creation, update, and deletion
include a compact event so your receiver can identify the changed link, even
after deletion:
{ "record": {"uuid": "contact-a", "objectName": "contact", "properties": {"firstname": "Jane"}}, "relations": {}, "event": { "type": "relation_created", "relationName": "works_at", "fromRecord": {"uuid": "contact-a", "objectName": "contact"}, "toRecord": {"uuid": "company-b", "objectName": "company"} }}Here the changed link is works_at from contact-a to company-b. Updates and
deletions use relation_updated and relation_deleted. record is the watched
endpoint; event.fromRecord and event.toRecord keep the original direction
even when watching the target. Same-object and self-links retain both endpoint
identities. Endpoints contain only uuid and objectName, never properties;
legacy events with missing endpoint or relation data omit those fields.
fields affects only the main record’s properties, not endpoint identity.
relations holds additionally requested records, not the changed link.
Use USER_FRIENDLY when your handler needs the normal event envelope. Set
fields: '' to clear a saved selection; omitting it means all available main
record properties. The CLI preserves these settings on pull and push.
Resume an automatically deactivated webhook
Section titled “Resume an automatically deactivated webhook”Restore the destination before resuming deliveries. Company record-event webhooks,
including record.<object>.date_due.<property> subscriptions, automatically stop
after three combined HTTP 400/404 responses, counting retry attempts. A 2xx
response resets the counter; other errors leave it unchanged. HTTP 410 stops the
subscription immediately. These rules apply to HTTP and serverless deliveries.
Only the affected company’s subscription stops; the webhook definition and other
companies remain. Lifecycle and app-bar hooks do not automatically deactivate,
and HTTP 410 no longer deletes webhook definitions on any delivery path.
With CLI 0.1.12 or later, inspect the selected company’s state:
caraer company select <company-uuid>caraer webhooks list --app <app-uuid>caraer webhooks show <webhook-uuid> --app <app-uuid>List/detail responses expose read-only deactivated, failureCount,
lastFailureStatus, lastFailureAt, deactivatedAt, and deactivationReason.
Timestamps are Unix milliseconds. Older subscriptions default to active with a
zero counter. The app’s Webhooks list and editor show Automatically deactivated,
the HTTP status and time, and a Reactivate action. A failed reactivation leaves
the warning visible.
After fixing and testing the destination, explicitly resume future deliveries:
caraer webhooks reactivate <webhook-uuid> --app <app-uuid>caraer webhooks show <webhook-uuid> --app <app-uuid>The caller needs the existing app-resource edit permissions and access to the
selected company’s subscription; knowing its UUID is insufficient. Expect
deactivated: false and failureCount: 0. Last incident metadata remains.
Send a new sandbox event and verify its delivery: reactivation does not replay
old queued events. Requests already in flight may finish, but their results
cannot change the reactivated state.
Tests remain available while deactivated and neither advance nor reset the
counter. Configuration edits, installation rematerialization, deployments, and
manifest enabled values preserve automatic deactivation. Use the explicit
reactivation action rather than pushing configuration to resume deliveries.
For these company subscriptions, 400/404/410 failures no longer retain full payloads in the dead-letter store (the store for failed deliveries). A pending 404 retry can retain its payload until success, retry exhaustion, or deactivation. Deactivation discards pending retries and date-scheduled work for that company. Other failures, including 413 and 5xx, keep their existing retry/dead-letter policy.
The API action is POST /api/v2/apps/{appUuid}/webhooks/{webhookUuid}/reactivate.
See the API reference and authentication guide for
Bearer authentication and company selection.
Python client 2.0.622 response migration
Section titled “Python client 2.0.622 response migration”The published Python client adds
ApplicationsApi.reactivate_app_webhook(app_uuid, webhook_uuid) and the runtime
status fields. ShowResponseSubscribeWebhookDTO.data now contains a
SubscribeWebhookDTO model instead of a dictionary. Replace dictionary access
such as response.data["deactivated"] with model attributes:
webhook = response.dataprint(webhook.deactivated, webhook.failure_count)For a successful reactivation response, expect False 0. Use
webhook.last_failure_status, webhook.last_failure_at, webhook.deactivated_at,
and webhook.deactivation_reason for incident details. to_dict() excludes these
read-only runtime fields, including deactivated and failure_count; inspect
the attributes directly. Configuration serialization cannot reactivate a webhook.