#Maintaining a bi-directional sync with external system

1 messages · Page 1 of 1 (latest)

worthy nest
#

What would the best Convex best practice way when our system requires a bidirectional sync between the Convex application and a CRM?

I can see from CRM -> Convex, that webhooks would likely be the best way. What about goingf rom Convex to CRM? In my current non-Convex version of the application, we are using AWS Eventbridge to publish events, and have a consumer write to the CRM. I'm curious what would be the best equivalent

spark blazeBOT
#

Hi @worthy nest. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

You’re right that CRM → Convex is best handled via webhooks into Convex HTTP actions, which then call internal mutations/queries. [REST & webhooks; Clerk webhook example]

For Convex → CRM, the closest equivalent to your “EventBridge + consumer” pattern is:

  1. Emit events into Convex tables from your core mutations

    • When something “CRM‑worthy” happens (e.g. contact created/updated), write a row into an events table in the same transaction as your main write.
    • This gives you a durable, queryable event log you can inspect and export. [Persist events]
  2. Use actions as the integration boundary to the CRM

    • Actions are the place to call external services (like your CRM’s REST API). [Actions & external services]
    • Pattern:
      • Mutation writes business data + event row.
      • Mutation schedules an action (or you have a polling/cron‑style action) that reads pending events and calls the CRM via fetch.
    • Keep actions small and focused on the non‑deterministic part (the HTTP call). [Actions guidance]
#
  1. Make the outbound calls robust (retry, backoff, idempotency)

    • For webhook‑style pushes to third parties, Convex recommends using an “action retrier” pattern to retry failures with backoff. [Webhook retrier example]
    • You can adapt that pattern to your CRM calls:
      • Store enough info in the event row to make the CRM operation idempotent (e.g. external IDs).
      • On failure, throw in the action so the retrier (or your own scheduler logic) can try again.
  2. Expose REST endpoints if the CRM needs to pull instead of receive pushes

    • If the CRM (or an intermediate integration platform) wants to call you, you can expose HTTP routes that wrap existing queries/mutations with ctx.runQuery / ctx.runMutation. [REST & webhooks]
  3. Optional: use external integration platforms for heavy analytics / ETL

    • If you need large‑scale analytics or complex sync logic, you can stream Convex tables to tools like BigQuery via Fivetran/Airbyte, and/or stream data into Convex from other systems. [Streaming import/export]

Putting it together, the Convex “best practice” analogue to EventBridge + consumer is:

  • Convex mutations + events table as your internal event bus.
  • Convex actions (possibly with a retry helper) as the outbound CRM sync worker.
  • Webhooks / HTTP actions for inbound CRM → Convex updates.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@worthy nest Kapa.ai is still learning and improving, please let me know how I did by reacting below.

worthy nest
#

@spark blaze can you explain more about the polling/cron-style action versus mutation scheduling an action? I would imagine it would be very easy for a mutation to accidentally forget to publish an event and fire off the action.

spark blazeBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

You’re right to think about “forgetting to publish” as a real risk. Convex gives you two main patterns here, and they have different guarantees.

1. Mutation → schedule action (recommended for correctness)

Pattern:

  • User‑facing mutation:
    • Writes your main data.
    • Writes an “event” row (or otherwise marks work to do).
    • Schedules an internal action or mutation via ctx.scheduler.runAfter(0, ...). [Scheduled functions; Pinecone example]

Key properties:

  • Atomic with the transaction: scheduling from a mutation is part of the same transaction. If the mutation commits, the scheduled function is guaranteed to exist; if the mutation fails, nothing is scheduled. [Scheduled from mutations]
  • Exactly‑once for mutations: if you schedule a mutation, Convex retries it on transient errors until it either succeeds or deterministically fails. [Error handling; Idempotency]
  • Separation of concerns: the mutation stays transactional and deterministic; the action does the non‑deterministic external call. [Actions & external services]

On your “forgetting to publish” concern: that’s a code‑level invariant, not a Convex one. The usual mitigation is to centralize the pattern:

#
  • Have a single internal mutation that both:
    • updates the domain record, and
    • writes the event + schedules the action.
  • Call that internal mutation from all public mutations that can change that entity.

That way, you don’t have to remember to “also publish” in many places; it’s baked into one function.

2. Polling / cron‑style worker

Pattern:

  • A cron job or scheduled mutation runs periodically.
  • It scans for “pending” work in a table and:

Key properties:

  • Reliable workflow via mutations: the cron “rescheduler” mutation is retried until success, and it can schedule actions to do the unreliable work. [User-space crons workflow; Error handling]
  • Decoupled timing: you don’t push to the CRM immediately; you process a backlog on a schedule.
  • Good for many producers: if lots of different mutations just mark “needs sync” in a table, a single cron worker can handle all of them.

This doesn’t by itself solve “forgetting to publish” either: you still need each mutation that should be synced to mark something in the table. But again, you can centralize that logic in shared internal mutations.

When to use which

From the docs and Stack posts:

#
  • Use mutation → schedule action when you want:
  • Use cron / polling when you want:
    • Periodic reconciliation or batch sync.
    • A central worker that repeatedly reschedules itself and triggers jobs, independent of any single user mutation. [User-space crons workflow; Crons usage]

In both cases, the “don’t forget to publish” problem is addressed by API design: wrap your domain changes in internal mutations that always also update the “to‑sync” state and/or schedule the work, so callers can’t accidentally skip it. Convex then gives you the transactional and scheduling guarantees on top.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: