Documentation

Webhooks

Verified messaging and funding webhook contracts, duplicate handling, and current reconciliation behavior.

Webhook replay
Endpoint https://example.com/textree message.delivered, message.failed, message.received
500 evt_123 failed
200 evt_122 delivered 842ms

Webhooks

Textree currently accepts provider callbacks from two boundaries:

  • messaging provider events
  • Peer for funding events

All webhook routes are JSON endpoints served by Phoenix.

Endpoints

  • signed messaging provider event callbacks
  • signed messaging provider inbound callbacks
  • POST /webhooks/peer/events

Customer webhook endpoints are configured from the authenticated Webhooks page. Provider webhook routes above are the inbound provider callbacks Textree uses to update message, opt-out, and funding state.

Customer event types

Subscribe your endpoint to the events your application needs:

  • message.sent
  • message.delivered
  • message.failed
  • message.received
  • conversation.created
  • conversation.updated
  • opt_out.created

Customer event shape

Customer webhook payloads use a stable envelope so receivers can route by type and inspect the domain object in data.

{
  "id": "evt_123",
  "type": "message.delivered",
  "created_at": "2026-04-28T14:00:00Z",
  "data": {
    "message": {
      "id": "msg_123",
      "status": "delivered",
      "phone_number": "+15551234567",
      "segments": 1,
      "cost_cents": 1
    }
  }
}

Return any 2xx response to mark delivery as successful. Non-2xx responses are retained for debugging and retry/replay from the console.

Signature verification

Webhook verification is mandatory in the current alpha.

  • Messaging provider requests must include the configured signature header
  • Peer requests must include x-peer-signature
  • Signature values use the format sha256=<hex>

The signature is computed as an HMAC-SHA256 over Textree’s canonicalized payload representation, not over raw request bytes.

Accepted response

A verified webhook that passes shape validation returns 202 Accepted:

{
  "id": "8fca25fd-d7b7-4f58-8bdd-d4ab4ca3ae97",
  "status": "received",
  "provider": "messaging_provider",
  "event_type": "message.delivered",
  "external_id": "provider_evt_123",
  "duplicate": false
}

If the same provider sends the same event_type and external_id again, Textree returns the existing record with "duplicate": true and does not enqueue duplicate downstream work.

Failure responses

  • 401 with {"error":"invalid_signature"} when verification fails
  • 422 with {"error":"invalid_webhook_payload"} when the request is signed but missing the required event identity fields

Replay from the console

Every failed customer webhook delivery should be replayable after you fix the endpoint. The replay flow keeps the original payload and records a new delivery attempt with response code, latency, and response body.

Webhooks → select endpoint → open failed delivery → Replay webhook

Messaging event handling

Messaging provider payloads are normalized from keys such as type, id, message_id, and metadata.

Reconciled message events

  • message.sent
  • message.delivered
  • message.failed

These update downstream message state and may populate provider identifiers on the stored message.

Reconciled opt-out events

  • recipient.opted_out
  • contact.opted_out
  • message.opted_out
  • message.unsubscribed

In V1, these create or update workspace-level suppressions with provider metadata. Workspace suppressions block one-off UI sends, developer API sends, MCP sends, and campaign deliveries for that phone number.

Peer event handling

Peer payloads are normalized from keys such as event, event_id, session_id, amount_cents, and metadata.

Reconciled funding events

  • funding.completed
  • funding.failed
  • funding.expired

Completed funding events update funding-session state and create idempotent ledger credits. Failed and expired funding events update the stored funding-session state for operator review.

Examples

Messaging delivered event

{
  "type": "message.delivered",
  "id": "provider_evt_123",
  "message_id": "msg_123"
}

Messaging opt-out event

{
  "type": "recipient.opted_out",
  "id": "provider_evt_456",
  "phone_number": "+15551234567",
  "message_id": "msg_123"
}

Peer funding event

{
  "event": "funding.completed",
  "event_id": "peer_evt_123",
  "session_id": "session_123",
  "amount_cents": 500
}

Operational notes

  • Provider events are persisted before asynchronous reconciliation.
  • Failed events remain visible in /app with failure reasons and replay controls.
  • Replay is intended for operator recovery after underlying data or provider issues are fixed.
  • Unknown but validly signed events may be stored and ignored until the domain layer supports them.