careCycle
Webhooks

Webhooks

Receive real-time careCycle events at your own HTTPS endpoints.

Webhooks let careCycle push events to your systems the moment they happen (when a customer is created or updated, or when a call finishes) so you can keep a CRM in sync, trigger downstream automations, or build your own reporting without polling the API.

Each event is delivered as a signed POST request to the endpoint(s) you register. careCycle only sends events to clients that have configured at least one endpoint, so there's nothing to opt out of if you don't use webhooks.

Setting up webhooks

Webhooks are managed from Developer Settings → Webhooks in the dashboard.

Enable webhooks

Go to the Webhooks page and click Get started. This provisions webhook delivery for your organization.

Add an endpoint

Click Add Endpoint, enter the HTTPS URL that should receive events, and optionally choose which events to subscribe to. Leave the event selection empty to receive every event type.

Verify delivery

Trigger an event (for example, update a customer) and open the endpoint to see the delivery under Message attempts. Click into any message to inspect the payload, the response your endpoint returned, and the request headers.

Endpoint URLs must be publicly reachable over HTTPS. You can use a tool like webhook.site to experiment before pointing webhooks at your own service.

Verifying signatures

Every request includes signature headers so you can confirm it genuinely came from careCycle and was not tampered with:

HeaderDescription
svix-idUnique identifier for the message
svix-timestampUnix timestamp (seconds) of the send
svix-signatureSpace-delimited list of HMAC-SHA256 signatures

Each endpoint has its own Signing Secret (prefixed with whsec_), visible on the endpoint's detail page. Verify the signature over the raw request body before trusting a payload. The signing scheme is the open Standard Webhooks spec, so you don't have to implement HMAC verification by hand; an official library does it for you.

Get the verification library

careCycle signs with the same scheme Svix uses, so the maintained svix libraries work directly. They're published on each language's standard package registry under the name svix (the standardwebhooks libraries are byte-for-byte compatible if you prefer the vendor-neutral name):

npm install svix
# or: pnpm add svix / yarn add svix
pip install svix
go get github.com/svix/svix-webhooks/go
gem install svix
composer require svix/svix
cargo add svix

Verify an incoming request

Pass the raw request body and the three svix-* headers to the library. It recomputes the HMAC with your signing secret and throws / returns an error if the signature doesn't match. On success you get the verified payload back.

import { Webhook } from "svix"

const wh = new Webhook(process.env.CARECYCLE_WEBHOOK_SECRET) // whsec_...

// `payload` MUST be the raw request body string, not the parsed object.
const event = wh.verify(payload, {
  "svix-id": req.headers["svix-id"],
  "svix-timestamp": req.headers["svix-timestamp"],
  "svix-signature": req.headers["svix-signature"],
})
from svix.webhooks import Webhook

wh = Webhook(os.environ["CARECYCLE_WEBHOOK_SECRET"])  # whsec_...

# `payload` is the raw request body (bytes or str), not the parsed dict.
event = wh.verify(payload, {
    "svix-id": request.headers["svix-id"],
    "svix-timestamp": request.headers["svix-timestamp"],
    "svix-signature": request.headers["svix-signature"],
})
import svix "github.com/svix/svix-webhooks/go"

wh, err := svix.NewWebhook(os.Getenv("CARECYCLE_WEBHOOK_SECRET")) // whsec_...
if err != nil {
    return err
}

// `body` is the raw request body bytes. `r.Header` carries the svix-* headers.
if err := wh.Verify(body, r.Header); err != nil {
    http.Error(w, "invalid signature", http.StatusBadRequest)
    return
}
require "svix"

wh = Svix::Webhook.new(ENV["CARECYCLE_WEBHOOK_SECRET"]) # whsec_...

# `payload` is the raw request body string.
json = wh.verify(payload, {
  "svix-id" => request.headers["svix-id"],
  "svix-timestamp" => request.headers["svix-timestamp"],
  "svix-signature" => request.headers["svix-signature"],
})
use Svix\Webhook;

$wh = new Webhook(getenv("CARECYCLE_WEBHOOK_SECRET")); // whsec_...

// $payload is the raw request body string.
$json = $wh->verify($payload, [
    "svix-id" => $_SERVER["HTTP_SVIX_ID"],
    "svix-timestamp" => $_SERVER["HTTP_SVIX_TIMESTAMP"],
    "svix-signature" => $_SERVER["HTTP_SVIX_SIGNATURE"],
]);
use svix::webhooks::Webhook;

let wh = Webhook::new(&secret)?; // whsec_...

// `body` is the raw request body bytes; `headers` is the http::HeaderMap.
wh.verify(&body, &headers)?;

Verify against the raw, unparsed request body. Re-serializing the JSON before verifying will change the bytes and the signature check will fail. Most web frameworks parse JSON automatically, so reach for the raw-body accessor (e.g. express.raw(), await request.body(), request.get_data()).

Delivery, retries, and replay

careCycle delivers webhooks through Svix, using the Standard Webhooks format described above.

  • Delivery is asynchronous. Events are queued after the change is saved and sent from a background worker, so a webhook can arrive after the API call that caused it has already returned.
  • Respond quickly with a 2xx. Any 2xx status is treated as success. Do your heavy processing asynchronously and acknowledge fast.
  • Failed deliveries are retried automatically with exponential backoff. Every attempt, with the status code and response your endpoint returned, is listed under Message attempts on the endpoint page.
  • Replay anytime. From the endpoint or message detail page you can resend an individual message to that endpoint, which is useful after fixing an outage on your side.
  • Order is not guaranteed. Events are processed concurrently and retried independently. Use the event payload (for example, the resource id and its timestamps) rather than assuming events arrive in the order they occurred.

Handling duplicates

Automatic retries and manual resends redeliver the same message, so they carry the same svix-id header. Store the svix-id of each message you process and skip any you have already handled.

In rare cases, such as an internal retry, the same change can also arrive as two separate messages with different svix-id values. Make your handler idempotent on the payload itself. Not every event has a top-level id, so key on event_type plus the identifier and time for that event's shape:

EventsIdentifierTime
customer.created, customer.updated, policy.created, policy.updated, all appointment.* eventsidupdatedAt
call.completed, sms.sent, sms.receivedid (each call or message is its own record)createdAt
customer.unsubscribedcustomerIdunsubscribedAt
inbox.conversation_marked_donethreadIdmarkedDoneAt
inquiry.callback_scheduledinquiryIdcallbackRequestedAt

The same action can happen to a resource more than once: a conversation is marked done again after it reopens, a callback is scheduled again, or an appointment is rescheduled twice. Each repeat carries a new time, so include the time in the key. A message with the same event_type, identifier, and time as one you have handled is a duplicate; one with a newer time is new work.

For record events keyed on updatedAt, also ignore an update that is no newer than the version you already stored, since events can arrive out of order.

Example events

Send example on the endpoint page delivers a sample payload for an event type the endpoint subscribes to. The example goes through normal delivery, so every other endpoint in your organization that subscribes to the same event type receives it too. If a production endpoint subscribes to that event type, make sure its handler can recognize and ignore the placeholder values.

Next, see the full list of events and example payloads.

On this page