Payload examples, signature verification, and sample receivers.
Website · Developer Docs · Webhook Reference · Support
Webhooks are how TextConvo tells you what happened: a lead was accepted, a message was delivered, someone replied, someone opted out, a journey pushed its result to your CRM.
This repository gives you the payload shapes, four verification implementations, and two runnable receivers. No platform code — just what you need on your side of the connection.
The event catalogue and header reference are maintained at textconvo.ai/docs#webhooks. This repository shows you what to do with them.
| Path | What it is |
|---|---|
| payloads/ | One JSON file per event, exactly as delivered |
| verification/ | Signature verification in Node.js, Python, PHP, and Go |
| receivers/ | Runnable receivers: Express and Flask |
| docs/RETRIES.md | Retry behaviour, idempotency, and how to build a receiver that survives them |
| Header | Purpose |
|---|---|
X-TextConvo-Signature |
Signature in the form sha256=<hex> |
X-TextConvo-Timestamp |
Unix timestamp used in the signed string |
X-TextConvo-Source-Key |
Which source the webhook belongs to |
Recompute HMAC-SHA256 over timestamp + "." + rawBody using your webhook secret, hex-encode it, and compare against the header value with the sha256= prefix stripped.
1. Verify before you parse. Read the raw body as bytes, verify the signature against those exact bytes, and only then deserialise. Most frameworks parse and discard the raw body by default — every receiver here shows how to keep it.
2. Compare in constant time. Use crypto.timingSafeEqual, hmac.compare_digest, hash_equals, or hmac.Equal. A plain string comparison leaks timing information.
3. Reject stale timestamps. Anything outside a few minutes should be refused, even with a valid signature. That is what stops a captured request being replayed later.
4. Answer fast, work later. Return 2xx as soon as the signature checks out, then process asynchronously. Slow receivers cause retries, and retries cause duplicates.
5. Deduplicate. The same event can arrive more than once. Key your processing on the identifiers in the payload and make handlers idempotent. See docs/RETRIES.md.
| Event | Meaning |
|---|---|
lead.accepted |
Lead accepted, matched or created as a contact |
lead.rejected |
Lead rejected by validation or deduplication |
lead.delivered |
Message delivered to the carrier |
lead.failed |
Message failed to deliver |
lead.reply |
Contact replied |
lead.click |
Contact clicked a tracked link |
lead.opt_out |
Contact opted out; suppression applies automatically |
lead.sent |
Email message sent |
lead.crm_updated |
Inbound CRM change reflected into TextConvo |
journey.crm_pushed |
Journey outcome pushed to your CRM |
scheduled_call.confirmed |
A scheduled call was confirmed |
scheduled_call.cancelled |
A scheduled call was cancelled |
support.call_me_now |
Contact asked for an immediate callback |
support.call_back_later |
Contact asked for a callback later |
Every body includes an event field naming the event. Switch on it, and ignore events you do not handle rather than erroring on them — new events can be added at any time.
# Node.js / Express
cd receivers
npm install express
export TEXTCONVO_WEBHOOK_SECRET=your_webhook_secret
node express-receiver.js
# Python / Flask
pip install flask
export TEXTCONVO_WEBHOOK_SECRET=your_webhook_secret
python flask_receiver.pyExpose it with a tunnel, configure the URL in TextConvo, then send yourself a test lead using textconvo-api-examples.
SECRET=your_webhook_secret
TS=$(date +%s)
BODY=$(tr -d '\n ' < payloads/lead.accepted.json)
SIG=$(printf '%s' "$TS.$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -X POST http://localhost:3000/webhooks/textconvo \
-H "Content-Type: application/json" \
-H "X-TextConvo-Timestamp: $TS" \
-H "X-TextConvo-Signature: sha256=$SIG" \
-H "X-TextConvo-Source-Key: test-source" \
-d "$BODY"A receiver that accepts this and rejects the same request with a tampered body is working correctly.
| Repository | Purpose |
|---|---|
| textconvo-api-examples | Sending leads in nine languages |
| textconvo-openapi | Webhook payloads as OpenAPI 3.1 schemas |
| textconvo-sample-apps | Receivers inside complete demo apps |
Submit the contact form and you get a direct line to Ria, the TextConvo AI orchestrator — call her for a live voice demo, or text her and watch the SMS AI reply in real time. A human follows up within one business day, and the same form is how API credentials, a source key, and a webhook secret are issued.
Handed a TextConvo QR code at an event or in a demo? Scanning it opens the same conversation. The form is simply the path that works for everyone.
A receiver for your framework would be genuinely useful — FastAPI, Rails, Laravel, Spring, ASP.NET, Cloudflare Workers. See CONTRIBUTING.md.
If you find a flaw in a verification example, report it privately — SECURITY.md. Never paste a real webhook secret in an issue.
MIT © TextConvo