LearnUse Webhooks to Update Existing Flows

Use Webhooks to Update Existing Flows

Webhooks are the bridge between Causet and existing apps.

  • Causet can emit (or publish) updates after important workflow state changes
  • Existing apps use those updates to refresh their own DB / projections
  • The existing frontend can keep reading existing tables
  • Async workflows do not require a full app rewrite

Start with Retrofit an Existing App if you are wrapping an endpoint first.

Availability: Inbound webhook → intent and managed outbound HTTP webhook delivery are both Available now. You can also bridge with Kafka ledger consumers or Causet queries/projections. See What runs today?.


Two directions

DirectionRoleStatus
InboundProvider → your app → Causet intentAvailable now
Outbound bridgeCauset event → your app DB/UIAvailable now via HTTP webhooks, Kafka, or queries

Why webhooks matter for retrofitting

Existing apps already receive async updates. Handlers often become mini workflow engines — verify, update state, email, notify, retry.

Causet moves the durable workflow out of the handler. The handler stays thin.

Before:

POST /api/webhooks/stripe
  → verify signature
  → update subscription
  → send receipt
  → notify Slack
  → maybe fail halfway

After:

POST /api/webhooks/stripe
  → verify signature
  → submit RECORD_PAYMENT intent
  → return 200 quickly

Causet
  → update subscription state
  → emit events
  → timeline records each step

Your app can then update its own tables from those events (consumer or future HTTP webhook).


Inbound: thin adapters

Webhook handlers should usually:

  1. Verify the signature
  2. Parse and normalize the payload
  3. Extract a correlation / entity ID
  4. Submit a Causet intent (with provider event ID as idempotency key)
  5. Return 2xx quickly

They should not send five emails, call three APIs, or hide retry logic in the route.

Webhooks can start a workflow

Stripe checkout.session.completedRECORD_PAYMENT → fulfillment timeline starts.

Webhooks can advance a workflow

DocuSign signed → RECORD_DOCUMENT_SIGNED → onboarding saga resumes on the same entity.


Outbound: update the existing app

After Causet commits meaningful events, your existing system needs the new status.

Pattern A — Causet projections / queries (Available now)

Your API reads Causet for workflow status; frontend may still use your tables for other data.

Pattern B — Kafka / event consumer (Available now)

Subscribe to ledger events and update your DB:

async function handleCausetEvent(event: {
  eventId: string
  type: string
  requestId: string
  status?: string
}) {
  if (await alreadyProcessed(event.eventId)) return // ignore duplicates
 
  switch (event.type) {
    case 'access_approved':
      await db.accessRequests.update(event.requestId, { status: 'approved' })
      break
    case 'payment_dispute_opened':
      await db.disputes.update(event.requestId, { status: 'open' })
      break
    case 'ticket_escalated':
      await db.tickets.update(event.requestId, { status: 'escalated' })
      break
    case 'ai_decision_emitted':
      await db.agentActions.insert({
        ticketId: event.requestId,
        decision: event.status,
      })
      break
  }
 
  await markProcessed(event.eventId)
}

Pattern C — Outbound HTTP webhooks (Available now)

Same handler shape as above, delivered as HTTP callbacks from Causet’s webhook delivery service.


Examples

Access request status update

Causet emits: access_approved
Your app:     UPDATE access_requests SET status = 'approved' WHERE id = …
Frontend:     still reads access_requests

Payment dispute update

Causet emits: payment_dispute_opened → evidence_requested → dispute_won
Your app:     updates disputes table per event

Support ticket escalation update

Causet emits: ticket_escalated
Your app:     sets tickets.status = 'escalated', notifies on-call

AI decision emitted as a business event

Causet:        op: decision → emits TICKET_AI_TRIAGED
Your app:     stores triage label / confidence on the ticket row
Timeline:     decision + tool calls remain inspectable in Causet

Retry and idempotency

Treat deliveries as at-least-once.

PracticeWhy
Include event IDs in payloadsStable dedupe key
Make handlers idempotentSafe retries
Store last processed event (or set of IDs)Ignore duplicates
Safely ignore duplicatesSame event twice must not double-update
Retry failed deliveriesTransient HTTP/DB errors

Inbound intents: pass the provider event ID as the Causet idempotency key.

await client.emit(
  'access_request_stream',
  requestId,
  'RECORD_CRM_ENRICHMENT',
  { …payload, providerEventId: event.id },
  event.id, // idempotency key
)

Outbound consumers: key off Causet eventId / cursor, not “process every message blindly.”


Correlation IDs

Use the same entity ID across the original intent and later webhooks (requestId, orderId, stripeSessionId, etc.). See Correlation and Causation.


Conceptual inbound handler

export async function POST(req: Request) {
  const rawBody = await req.text()
  verifySignature(rawBody, req.headers.get('x-signature'))
 
  const event = JSON.parse(rawBody)
  const requestId = await lookupRequestId(event.external_id)
 
  const client = createServerCausetClient()
  await client.init()
  try {
    await client.emit(
      'access_request_stream',
      requestId,
      'RECORD_EXTERNAL_UPDATE',
      { …normalize(event), providerEventId: event.id },
      event.id,
    )
  } finally {
    client.destroy()
  }
 
  return Response.json({ received: true })
}

Full DSL example: Webhook Processing.