Skip to main content
    All case studies
    Case study · Rate-limit mitigation
    Meridian Operations · external system rate limits

    10× fewer API calls. 200-ms lookups. Zero rate-limit hits.

    Trade one-time engineering effort for permanent rate-limit immunity. Sweeps query a local PostgreSQL copy built from the webhook payloads the source system was already firing — and consulted live only before writes.

    0x
    Fewer live API calls during sweep cycles
    0 ms
    Median lookup time vs. 1.4 s live
    0
    Rate-limit hits since the mirror went live
    Built with
    Zoho DeskZohoDeskMicrosoft AzureAzurePostgreSQLPostgresDrizzle ORMDrizzleORM
    The problem

    Four sweeps fired at 8:00, the rate limit hit at 8:04, and the first error of the day was a 429

    The team was receiving real-time webhooks from the source system, logging them, and discarding them — then querying the same source system for data it had already delivered. Sweeps ran into per-minute rate ceilings on busy mornings. A live API call averaged 1.4 seconds; a 300-candidate scan took 7 minutes before any classification started. By the time the last ticket was classified, the first one was already stale.

    • The 8:04am rate limit
      Four sweeps firing within the same two-minute window generated enough candidate-discovery calls to saturate the per-minute rate limit. The first sweep got results. The rest got 429s. Operators handled the backlog manually while sweeps sat waiting to retry.
    • Latency compounding
      A live API call averaged 1.4 seconds. A sweep scanning 300 candidate tickets took 7 minutes before any classification started. By the time the last ticket was classified, the first ticket's state was stale.
    • No indexed queries on the source system
      The external system's search API does not support indexed filtering by arbitrary custom fields. Finding 'all tickets with carrier code AND shipment reference AND not touched in 24 hours' meant fetching everything and filtering server-side.
    • Webhook data going unused
      Zoho was already firing real-time webhooks for every ticket and attachment event. The payloads contained the full state needed to answer most sweep questions. The team was receiving them, logging them, and discarding them.
    Pre-mirror morning · live API only
    429 cascade
    08:00:02Sweep A fires · 78 candidatesok
    08:00:14Sweep B fires · 142 candidatesok
    08:01:31Sweep C fires · 91 candidatesok
    08:02:58Sweep D fires · 64 candidatesthrottled
    08:04:12Rate limit hit · queue backed up429
    08:07:48Operator opens first ticket429
    Webhooks delivered · then ignoredFirst op action: 429
    The pipeline

    From inbox to verified record in one pass

    Webhook in, indexed mirror out. Live API calls reserved for pre-write confirmation reads only.

    01Webhook
    Zoho fires on ticket events
    Webhook payload posts to the mirror endpoint on every Ticket, Comment, Thread, or Attachment event. Receiver lives in Azure Functions.
    Zoho Desk
    02Verify
    JWT signature checked against JWKS
    Every payload verified against Zoho's JWT signature using the public JWKS endpoint. Unsigned payloads dropped before any database write. Adds <10 ms.
    Azure Functions
    03Upsert
    Idempotent write to mirror tables
    Verified payload written to one of four mirror tables: tickets, comments, threads, or attachments. Duplicate webhooks for the same entity version are no-ops; deletes soft-delete with audit history preserved.
    PostgreSQL
    04Promote
    Six fields lifted to indexed columns
    Carrier code, shipment reference, account tax ID, last-update timestamp, submission reference, and delivery-order status are extracted from the JSON blob into dedicated indexed columns. Sub-5ms queries against 20,000 rows.
    PostgreSQL
    05Backfill
    Delayed thread fetch on new tickets
    On Ticket-Add, threads aren't always indexed on Zoho's side immediately. A 10-second delayed backfill fetches threads after Zoho has had time to index them.
    Azure Functions
    06Serve
    Sweeps query in-process; live API only confirms writes
    Candidate queries run against the indexed mirror in-process. The live Zoho API is consulted only for confirmation reads — immediately before any write — guaranteeing the action targets current state, not stale mirror data.
    Indexed lookup path
    Mirror returns a candidate set in 200 ms
    Sweep's candidate query hits promoted columns. Results return at 200 ms median. No Zoho API call. Sweep proceeds to classification with a full candidate list.
    Authoritative-write path
    Live Zoho call confirms before any field is written
    For each candidate the sweep intends to act on, a live Zoho call confirms current state. If confirmation differs from the mirror — ticket closed, status changed, field updated since the last webhook — the sweep skips and logs the discrepancy. The mirror updates on the next webhook.
    Validation review

    Freshness confidence per query type

    Each sweep runs a different candidate query. Confidence reflects how reliably the indexed mirror represents current state for that query.

    Field-level confidence
    Pass 2 — Claude self-review
    Carrier + reference lookupFilter delivery-order sweep candidates
    99%High
    Declarations queue with attachmentsFilter declaration-detection candidates
    97%High
    24-hour stamp lookupExclude tickets the sweep already touched
    98%High
    Status Docs NeededFilter docs-readiness sweep candidates
    95%High
    Account tax ID lookupCross-sweep dedup by account
    66%Low
    Routed to human review. Account tax ID is occasionally edited by the ticket owner after the ticket is created. The mirror updates on the next webhook, but a sweep firing in the gap acts on the prior value. The live confirmation step before any write is the mitigation — the confirmation call catches the edit before the sweep takes action.
    4 of 5 fields cleared the 0.85 threshold
    model: Mirror freshness
    The stack

    Boring tech, glued together well

    Each vendor handles what it's best at. Aisyst owns the orchestration layer in between.

    Zoho DeskZohoDesk
    Zoho Desk
    Source system — webhook emitter and live confirmation target before writes
    Microsoft AzureAzure
    Azure Functions
    Webhook receiver, idempotent upsert handler, delayed backfill trigger
    PostgreSQLPostgres
    PostgreSQL
    Mirror tables for tickets, comments, threads, attachments — six promoted indexed columns
    Drizzle ORMDrizzleORM
    Drizzle ORM
    Type-safe queries against the indexed mirror columns from sweep code

    Third-party logos are trademarks of their respective owners and appear here only to indicate integration.

    Outcomes

    What changed when the source system stopped being a bottleneck

    The mirror trades one-time engineering effort for permanent rate-limit immunity on the discovery path. The live API is reserved for the calls that genuinely require it.

    0x
    Fewer live API calls during sweep cycles
    0 ms
    Median mirror lookup vs. 1.4 s live
    0
    Rate-limit hits since launch
    0.0%
    Mirror freshness within 2 seconds
    Watch p95 mirror freshness lag, not the median

    Median freshness is 99.4% within 2 seconds. The 0.6% that miss the window are delayed webhooks; the live confirmation step catches those before any write. If p95 climbs above 5 seconds, the upsert path is congested — webhook volume spike, slow PostgreSQL write (index bloat, lock contention), or a backfill queue backing up. Individual slow inserts are less dangerous than a sustained shift in the tail.

    If you're rate-limited against an external system you query a lot, this pattern fits

    Mirror it locally with the webhook payloads you already receive. We'll scope it on a 30-minute call.