# Guide to Census (Fivetran Activations) Webhooks: Features and Best Practices

Census (Fivetran Activations) webhooks tell your systems what is happening to your reverse-ETL syncs: a sync run is queued, starts, succeeds or fails, or a sync alert is raised or resolved. Census is now part of Fivetran, which sells it as Fivetran Activations. It moves data from your warehouse into business tools, and these Sync Lifecycle Webhooks are how you hook its sync status into your own alerting, orchestration or reporting.

This guide covers how Census webhooks are configured and signed, the events and payloads, what the documentation leaves out, and the best practices for running a Census receiver in production.

## What are Census webhooks?

Census webhooks are configured per workspace. Each webhook has a name, an endpoint and the events it subscribes to. When a subscribed event happens, Census sends a small JSON payload that identifies the workspace, the sync and the sync run, and names the event.

Every request carries an `X-Signature` header, a base64 HMAC-SHA256 of the raw body keyed with the webhook's secret. The secret is shown once, when you create the webhook.

Census's documentation now lives at fivetran.com under Activations, and the alert payloads in those docs still link to `app.getcensus.com`. These webhooks are separate from Fivetran's connector webhooks, which use a different header (`X-Fivetran-Signature-256`) and a hex signature. If you receive both, verify them separately.

## Census webhook features

| Feature | Details |
| --- | --- |
| Configuration | Workspace settings, **Webhooks** tab, or the Activations webhooks REST API |
| Subscription model | One or more events per webhook |
| Signature header | `X-Signature` |
| Scheme | HMAC-SHA256, standard base64 |
| Signed content | The raw request body only |
| Secret | One per webhook, shown once at creation |
| Replay window | None. No timestamp is sent or signed |
| Event ID | None |
| HTTP method and content type | Not stated in the docs. The examples are JSON bodies |
| Timeout and retries | Not documented |
| Source IPs | Not published |

## Common events

Census has two groups of events:

| Group | Event | Fires when |
| --- | --- | --- |
| Sync alerts | `sync.alert.raised` | Any subscribed sync alert in your workspace is triggered |
|  | `sync.alert.resolved` | Any subscribed sync alert is resolved |
| Sync runs | `sync.triggered` | A sync run has been queued |
|  | `sync.started` | Active work on the sync run has started |
|  | `sync.completed` | The sync run has completed, successfully or not |
|  | `sync.success` | A sync run completed successfully |
|  | `sync.failed` | A sync run failed |

Every payload is a flat object. These fields are always present:

```json
{
  "workspace_id": 3167,
  "sync_id": 3167956,
  "sync_run_id": 324402082,
  "event": "sync.failed"
}

```

`sync.completed` adds `status`, which is `ok` for a successful run and `emergency` for a failed one. The alert events add `alert_instance_id`, `alert_type` (for example `FailureAlertConfiguration`), `message`, `details` and `followup_url`, a link to the sync's history in the Census app. Here is Census's `sync.alert.raised` example, with the docs' line wrapping removed:

```json
{
  "workspace_id": 3167,
  "sync_id": 3167956,
  "sync_run_id": 324402082,
  "alert_instance_id": 626248,
  "alert_type": "FailureAlertConfiguration",
  "message": "Sync Failed",
  "details": "Sync has failed with error: A mapped source column has been deleted: email",
  "followup_url": "https://app.getcensus.com/workspaces/3167/syncs/3167956/sync-history",
  "event": "sync.alert.raised"
}

```

The IDs are integers. Because `sync.completed`, `sync.success` and `sync.failed` overlap, subscribing to all three gives you two events at the end of every run. Pick `sync.completed` with its `status` field, or the `sync.success` and `sync.failed` pair.

## Setting up Census webhooks

1. In Census (Fivetran Activations), open **Workspace settings** and go to the **Webhooks** tab.
2. Click **Add Webhook**.
3. Enter a **Name**, an optional **Description** and the **Endpoint** URL, and subscribe to at least one event.
4. Click **Create**. Copy the one-time secret straight away and store it server-side, for example as `CENSUS_WEBHOOK_SECRET`. Census won't show it again.

You can also list and manage webhooks through the Activations REST API.

If you use Hookdeck, create a source with the Census source type, set the secret on it, and use the Hookdeck source URL as the endpoint.

## Securing Census webhooks

Compute an HMAC-SHA256 of the raw request body, using the secret's bytes as the key, encode it as standard base64, and compare it in constant time with `X-Signature`. This follows Census's own Python sample.

```javascript
const crypto = require('crypto');
const express = require('express');

function verifyCensusSignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret) return false;

  const expected = Buffer.from(crypto.createHmac('sha256', secret).update(rawBody).digest('base64'), 'utf8');
  const received = Buffer.from(signatureHeader.trim(), 'utf8');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

const app = express();

app.post('/webhooks/census', express.raw({ type: '*/*' }), (req, res) => {
  if (!verifyCensusSignature(req.body, req.get('x-signature'), process.env.CENSUS_WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // Route on event.event, then process async.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import base64
import hashlib
import hmac
import os

def verify_census_signature(raw_body: bytes, signature_header, secret: str) -> bool:
    if not signature_header or not secret:
        return False
    expected = base64.b64encode(hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).digest())
    return hmac.compare_digest(signature_header.strip().encode("utf-8"), expected)

# In FastAPI: raw_body = await request.body()
# verify_census_signature(raw_body, request.headers.get("x-signature"),
#                         os.environ["CENSUS_WEBHOOK_SECRET"])

```

Census's sample passes the header straight to `hmac.compare_digest`, which raises a `TypeError` when the header is missing. The version above returns `False` instead.

To check your verifier, use the secret `census-test-secret` and this exact body:

```
{"workspace_id":3167,"sync_id":3167956,"sync_run_id":324399613,"event":"sync.success"}

```

The `X-Signature` header should be:

```
UIrFwdyskAsq/9SV2lnvbrVt51aBHJVV4QO4vXp+U7E=

```

> **Make Census webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) verifies the `X-Signature` header on every Census request and durably queues sync alerts and run events, so a sync failure is never missed because your alerting service was down.

## Census webhook limitations and pain points

### Delivery behaviour is undocumented

**The Problem:** You don't know how long Census waits for a response, whether it retries, or what counts as a failure.

**Why It Happens:** The Sync Lifecycle Webhooks page documents the events, payloads and signature, but nothing about timeouts, retries or ordering. It doesn't even name the HTTP method.

**Workarounds:**

* Respond with a `2xx` quickly, and assume a slow or failed response may mean a lost event.
* Reconcile from the sync history in Census, or its API, for any sync that matters.

**How Hookdeck Can Help:** Hookdeck accepts each request as soon as Census sends it and stores it, then retries delivery to your service on a schedule you set, so your own downtime doesn't depend on a retry policy you can't see.

### The secret is shown once

**The Problem:** You can't verify deliveries because the secret wasn't saved.

**Why It Happens:** Census shows the secret only when you click **Create**.

**Workarounds:**

* Copy it into your secret store before closing the dialog.
* If it is lost, create a new webhook and delete the old one.

**How Hookdeck Can Help:** Set the secret on the Hookdeck source once, and every service behind Hookdeck receives verified events without needing the secret itself.

### No event ID

**The Problem:** You can't tell a duplicate delivery from a new event.

**Why It Happens:** Payloads carry the workspace, sync and run IDs and the event name, but no ID for the event or the delivery.

**Workarounds:**

* Use `sync_run_id` plus `event` as the idempotency key for run events.
* For alerts, use `alert_instance_id` plus `event`.

**How Hookdeck Can Help:** Hookdeck's deduplication can drop repeated requests before they reach your service, based on the fields you choose, such as `sync_run_id` and `event`.

### Overlapping completion events

**The Problem:** Your alerting fires twice for every failed sync.

**Why It Happens:** A failed run produces `sync.completed` with `status: "emergency"` and `sync.failed`, and a sync alert can add `sync.alert.raised` on top.

**Workarounds:**

* Subscribe to only one of the completion styles.
* Use alert events for paging, and run events for metrics, rather than both for the same purpose.

**How Hookdeck Can Help:** Hookdeck filters can route `sync.failed` and `sync.alert.raised` to different destinations, so each event goes where it's needed.

## Best practices

### Verify against the raw body

The signature covers the exact bytes Census sent. Read the raw body (`express.raw()`, `await request.body()`) and parse only after verification.

### Route on `event`

The payload's `event` field names the event. Switch on it, and log and acknowledge anything you don't recognise.

### Fetch details from Census

Run events only carry IDs. When you need row counts, errors or timing, look up the sync run in Census using `sync_id` and `sync_run_id`, or send people to `followup_url` from alert events.

### Acknowledge fast and process asynchronously

Return a `2xx` as soon as the event is verified and stored, and do the rest afterwards. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Deduplicate deliveries

Use `sync_run_id` with `event`, or `alert_instance_id` with `event`, as an idempotency key. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

## Conclusion

Census signs its Sync Lifecycle Webhooks with a base64 HMAC-SHA256 of the raw body in `X-Signature`, keyed with a secret you only see once. Save the secret when you create the webhook, verify against the raw bytes, deduplicate on `sync_run_id` and `event`, and choose one way of detecting completion. Census doesn't document its delivery behaviour, so plan for reconciliation.

[Hookdeck Event Gateway](https://hookdeck.com) sits in front of your receiver, verifies every Census signature, and holds each sync event until your service has processed it.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Census webhooks reliably in minutes.