Skip to content

Documentv1.0.0

Updated

Receive Drip outcome callbacks

Preview contract

This guide follows the current Drip callback contract. Confirm rollout status with Dinodial before using it for a production campaign.

Drip sends a signed callback when a contact reaches a terminal outcome. This guide covers the complete integration: deploy a receiver, connect it to a process, place a rehearsal call, verify delivery, and recover callbacks that could not be delivered normally.

The callback receiver must:

  • accept POST requests over HTTPS;
  • use X-Dinodial-Pulse-Process-Id only to select the registered process key;
  • verify X-Dinodial-Pulse-Signature against the unmodified request body;
  • require the signed body's process_id to match the process-ID header;
  • reject stale, future, malformed, or incorrectly signed requests;
  • store each outcome idempotently using dd_pulse_id;
  • return 2xx only after durable persistence succeeds; and
  • keep the per-process callback secret outside source control and logs.

Callback lifecycle

  1. You create a Drip process and receive its callback secret once.
  2. You register the process and store its secret in your receiver's secret manager.
  3. Each inserted contact supplies the receiver's callback URL.
  4. Pulse sends one signed callback when the contact becomes terminal.
  5. Your receiver verifies and persists the outcome before returning 2xx.
  6. If delivery fails, you reconcile it through the authenticated failed-callback API.

Terminal statuses include answered, busy, no-answer, DND, carrier failure, and platform failure. See Drip integration for the payload, status vocabulary, identity rules, HMAC wire contract, and reconciliation semantics.

Use the reference receiver

Dinodial provides a downloadable reference receiver for Cloudflare Workers. It uses one encrypted Worker Secret per Drip and D1 for the enabled-process allow-list plus durable, idempotent outcome storage.

View or download the callback receiver on GitHub

Clone it locally:

sh
git clone https://github.com/dinodial/dinodial-drip-callback-receiver.git
cd dinodial-drip-callback-receiver

The repository contains the complete implementation, database migration, tests, and an example Wrangler configuration. This guide focuses on configuring and operating it rather than reproducing its source.

The reference deployment uses:

Cloudflare featurePurpose
WorkersPublic HTTPS callback endpoint and signature verification
Worker SecretsOne encrypted callback secret per process
D1Enabled process IDs and durable, idempotent callback history; never secrets
WranglerBuild, migration, deployment, live tail, and authenticated queries

It does not require KV, R2, Queues, Durable Objects, or a public outcome-list endpoint.

Prerequisites

Install Rust, the WebAssembly target, the pinned Workers build tool, and Wrangler. Then authenticate Wrangler against the Cloudflare account that will own the Worker and D1 database.

sh
rustup target add wasm32-unknown-unknown
cargo install worker-build --locked --version 0.8.5
wrangler --version
wrangler login
wrangler whoami

You also need:

  • the current vox CLI configured for the intended workspace;
  • a published agent;
  • a healthy Vox runtime;
  • an outbound transport DID;
  • a model-partner credential; and
  • explicit approval before inserting a billable contact.

Verify the CLI and workspace before making changes:

sh
vox version check
vox capabilities
vox workspace current
vox workspace check
vox account get
vox catalog vox-servers
vox catalog transport-dids
vox catalog partner-credentials

Verify the downloaded receiver

Run the repository's checks before deploying it:

sh
cargo fmt --check
cargo test
cargo clippy --all-targets -- -D warnings
worker-build --release

The tests exercise valid signatures, body tampering, an incorrect secret, stale and future timestamps, malformed signature fields, and every supported terminal status.

Create and configure D1

Create an APAC D1 database:

sh
wrangler d1 create dinodial-drip-callbacks --location apac

Copy the example configuration and replace <D1_DATABASE_ID> with the returned database_id:

sh
cp wrangler.example.jsonc wrangler.jsonc

Use a Worker name and D1 database name appropriate for the environment. Keep separate production and non-production deployments.

Validate the bundle and apply the migration:

sh
wrangler deploy --dry-run
wrangler d1 migrations apply dinodial-drip-callbacks --remote

Confirm the callback table is empty:

sh
wrangler d1 execute dinodial-drip-callbacks \
  --remote \
  --command 'SELECT COUNT(*) AS callback_count FROM callback_events'

Deploy the receiver

Deploy and record the generated workers.dev URL:

sh
wrangler deploy
wrangler deployments list \
  --name dinodial-drip-callback-receiver \
  --json

Check the health endpoint:

sh
curl -fsS https://dinodial-drip-callback-receiver.<account-subdomain>.workers.dev/health

Expected response:

json
{"status":"ok"}

Confirm that an unsigned callback is rejected:

sh
curl -i \
  -X POST \
  -H 'Content-Type: application/json' \
  --data '{}' \
  https://dinodial-drip-callback-receiver.<account-subdomain>.workers.dev/callbacks

Expected status: 401 Unauthorized. The rejected request must not create a D1 row.

Create the Drip process

Read the exact agent and infrastructure records before creating the process:

sh
vox agent get --agent-id <AGENT_GENESIS_UUID>
vox catalog vox-servers
vox catalog transport-dids
vox catalog partner-credentials

Preview the process name, region, agent, Vox runtime, transport DID, partner credential, VAD mode, timing, and retention settings for the approving operator. Then create exactly one running process:

sh
vox process create-drip \
  --agent-id <AGENT_GENESIS_UUID> \
  --name 'Callback tutorial drip' \
  --region in \
  --vox-server-id <HEALTHY_VOX_SERVER_UUID> \
  --transport-did-id <OUTBOUND_DID_UUID> \
  --partner-credentials-id <PARTNER_CREDENTIAL_UUID> \
  --vad-mode vox_vad_lite \
  --lens-ttl-days 30 \
  --hard-close-secs 180 \
  --idle-prompt-secs 12 \
  --idle-response-secs 10 \
  --confirm-mutation process.drip.create

Use the agent genesis by default. Each new call then resolves the latest active version in that lineage. Supply an exact version ID only when the process must remain pinned to that version.

The response contains the process id and a one-time callbackSecret. The API key and callback secret have different roles: the API key authenticates your calls to Pulse, while the callback secret authenticates Pulse's calls to your receiver.

Register the process and store its callback secret through Wrangler's hidden prompt:

sh
./scripts/register-process \
  <PROCESS_UUID> \
  dinodial-drip-callbacks \
  wrangler.jsonc

The script atomically reserves a disabled process row, derives a stable secret-binding name from the process UUID, and invokes Wrangler's secure prompt. Paste callbackSecret when prompted. The value goes to Cloudflare Worker Secrets; only after that succeeds does the script enable the process in D1. Never place the value in source control, shell history, screenshots, tickets, logs, or D1.

Cloudflare counts Worker Secrets toward the Worker variable ceiling: currently 64 on Free plans and 128 on Paid plans. Subtract other variables when sizing a receiver and use separate deployments before reaching the limit. Do not store plaintext callback secrets in D1 to bypass it. See Cloudflare Workers limits.

Confirm only the binding name is visible:

sh
wrangler secret list \
  --name dinodial-drip-callback-receiver \
  --format json

Read the process back and confirm that it is running with zero contacts:

sh
vox process get --process-id <PROCESS_UUID>

Place one rehearsal call

Create placeholders.json using the exact placeholders declared by the published agent:

json
{
  "customer_name": "Taylor"
}

Start a live Worker tail before inserting the contact:

sh
wrangler tail dinodial-drip-callback-receiver \
  --format pretty \
  --method POST

wrangler tail streams new invocations only. It does not replay historical requests. Keep that terminal open.

In a second terminal, show the approving operator the exact phone number, process ID, process-scoped unique_id, placeholders, and callback URL. Inserting a contact is billable and begins dialing immediately.

After approval, insert exactly one contact:

sh
vox process place-call \
  --process-id <PROCESS_UUID> \
  --unique-id tutorial-contact-0001 \
  --phone +12025550123 \
  --placeholders /absolute/path/to/placeholders.json \
  --confirm-mutation process.drip.placeCall \
  --callback-url <CALLBACK_URL>

Persist the returned unique_id and dd_pulse_id. When the contact terminates, the live tail should show a successful POST /callbacks invocation. The receiver deliberately does not log the callback body.

Read the durable outcome from D1:

sh
wrangler d1 execute dinodial-drip-callbacks \
  --remote \
  --command 'SELECT dd_pulse_id, process_id, unique_id, phone_number, status, duplicate_count, signature_timestamp FROM callback_events ORDER BY first_received_at_ms DESC LIMIT 20'

Verify that Pulse has no unacknowledged delivery failure:

sh
vox process failed-callbacks --process-id <PROCESS_UUID>

An empty failures array means Pulse accepted the receiver's 2xx response. For answered calls, obtain the signed Lens review URL with:

sh
vox process list-calls --process-id <PROCESS_UUID>

Interpret the evidence

EvidenceWhat it proves
Wrangler tail reports POST ... - OkThe Worker handled a live request and returned successfully.
D1 contains the callback rowThe signature and schema passed and the outcome was persisted.
duplicate_count = 0D1 observed one delivery for that dd_pulse_id.
Pulse reports no failed callbacksPulse received a successful response from the receiver.

With observability disabled, Wrangler tail is live-only. D1 is the durable callback history. If persistent request logs are required, enable Workers Logs deliberately and define retention and redaction policies before production use.

Recover failed deliveries

Do not rely only on normal webhook delivery. Periodically read the process's failed-callback queue through the authenticated Vox CLI, apply each terminal outcome idempotently using its dd_pulse_id, and acknowledge only the entries that were durably applied.

sh
vox process failed-callbacks --process-id <PROCESS_UUID>
vox process ack-failed-callbacks \
  --process-id <PROCESS_UUID> \
  --input /absolute/path/to/applied-failure-ids.json \
  --confirm-mutation process.drip.failedCallbacks.ack

Never acknowledge an entry before committing its outcome. A crash between acknowledgement and persistence would otherwise discard the recoverable copy.

Operate and update the receiver

Inspect the deployment, secret binding, outcome totals, and Pulse recovery queue:

sh
wrangler deployments list \
  --name dinodial-drip-callback-receiver \
  --json

wrangler secret list \
  --name dinodial-drip-callback-receiver \
  --format json

wrangler d1 execute dinodial-drip-callbacks \
  --remote \
  --command 'SELECT status, COUNT(*) AS total FROM callback_events GROUP BY status ORDER BY status'

vox process get --process-id <PROCESS_UUID>
vox process failed-callbacks --process-id <PROCESS_UUID>

Before deploying an update pulled from the reference repository:

sh
git pull --ff-only
cargo fmt --check
cargo test
cargo clippy --all-targets -- -D warnings
worker-build --release
wrangler deploy --dry-run
wrangler deploy

One reference Worker can support multiple concurrent processes because every registered process has a distinct encrypted Worker-secret binding. Do not replace an existing process binding while callbacks signed with its old secret can still arrive. The registration script deliberately refuses that accidental rotation.

Production checklist

  • Use dedicated receiver and database deployments for each environment.
  • Store the callback secret only in a secret manager.
  • Keep only process registration metadata, never callback secrets, in D1.
  • Accept callbacks only over HTTPS.
  • Verify the exact raw body before parsing JSON.
  • Reject future and stale signatures and compare HMACs in constant time.
  • Use dd_pulse_id as the idempotency key.
  • Return 2xx only after durable persistence succeeds.
  • Never log callback bodies or secrets.
  • Monitor and reconcile the failed-callback queue independently of normal delivery.
  • Decide whether persistent request logs are required and define redaction and retention first.

For the canonical payload, status vocabulary, signing rule, process-scoped identity model, and recovery contract, return to Drip integration.

Dinodial customer documentation