HomeAboutFeaturesPricingBlog
Sign inContact Us
All blogs

Building Real-Time Sync Between Slack and Your Task Manager

A technical deep-dive into how we built bidirectional sync between Slack and our ticket system using webhooks, message queues, and event-driven architecture.

Jordan
Jordan4 Feb 2026 · 8 min read

Introduction

One of the most requested features from our users was Slack integration. Not just notifications, but real bidirectional sync. When a ticket updates, Slack knows. When someone replies in Slack, the ticket updates.

Building this turned out to be more interesting than we expected. In this post, we will walk through our architecture, share some code, and explain the tradeoffs we made.

The Architecture Overview

Our sync system has three main components: a webhook receiver for Slack events, a message queue for reliable processing, and a sync engine that handles the bidirectional updates.

Here is the high-level flow:

Slack Event -> Webhook Receiver -> Message Queue -> Sync Engine -> Database
                                                          |
                                                          v
                                                    Ticket Updated
                                                          |
                                                          v
                                              Slack API <- Notification

This architecture ensures we never lose events, even during high load or temporary outages.

Handling Slack Webhooks

Slack sends events via HTTP POST to your webhook endpoint. The tricky part is responding quickly - Slack expects a 200 response within 3 seconds, or it will retry.

Our solution: acknowledge immediately, process asynchronously.

typescript
export async function handleSlackWebhook(req: Request) {
  const payload = await req.json();
  
  if (!verifySlackSignature(req, payload)) {
    return new Response("Invalid signature", { status: 401 });
  }
  
  if (payload.type === "url_verification") {
    return Response.json({ challenge: payload.challenge });
  }
  
  await messageQueue.publish("slack-events", {
    event: payload.event,
    teamId: payload.team_id,
    timestamp: Date.now(),
  });
  
  return new Response("OK", { status: 200 });
}

The key insight: separate receiving from processing. Your webhook handler should do almost nothing except queue the work.

The Sync Engine

The sync engine is where the magic happens. It consumes events from the queue and determines what action to take.

typescript
async function processSyncEvent(event: SlackEvent) {
  const { type, channel, message, thread_ts } = event;
  
  const ticket = await findLinkedTicket(channel, thread_ts);
  if (!ticket) return;
  
  switch (type) {
    case "message":
      await addCommentToTicket(ticket.id, {
        content: message.text,
        author: await resolveSlackUser(message.user),
        source: "slack",
      });
      break;
      
    case "reaction_added":
      if (message.reaction === "white_check_mark") {
        await updateTicketStatus(ticket.id, "done");
      }
      break;
  }
}

We use a simple convention: a checkmark emoji marks a ticket as done. This lets team members update ticket status without leaving Slack.

Preventing Infinite Loops

The biggest gotcha with bidirectional sync: infinite loops. Ticket updates Slack, Slack event triggers ticket update, which updates Slack, forever.

Our solution uses event sourcing with origin tracking:

typescript
await updateTicket(ticketId, {
  status: "done",
  _meta: {
    source: "slack",
    sourceEventId: event.event_ts,
  }
});

async function notifySlack(ticket: Ticket, change: Change) {
  if (change._meta?.source === "slack") {
    return;
  }
  
  await slack.chat.postMessage({
    channel: ticket.slackChannel,
    text: formatTicketUpdate(change),
    thread_ts: ticket.slackThreadTs,
  });
}

Every mutation carries metadata about its origin. Before propagating a change, we check if it came from the destination.

Handling Rate Limits

Slack has strict rate limits. We use exponential backoff and a token bucket algorithm:

typescript
const rateLimiter = new TokenBucket({
  capacity: 50,
  refillRate: 1,
  refillInterval: 1000,
});

async function postToSlack(message: SlackMessage) {
  await rateLimiter.acquire();
  return slack.chat.postMessage(message);
}

Other edge cases we handle: message ordering (using timestamps), deleted messages (soft-delete with indicator), and network failures (retry with backoff).

Monitoring and Debugging

Distributed systems are hard to debug. We built observability in from the start:

typescript
logger.info("sync.completed", {
  ticketId: ticket.id,
  slackChannel: channel,
  eventType: event.type,
  processingTimeMs: Date.now() - startTime,
  queueLatencyMs: startTime - event.timestamp,
});

We track queue depth, processing latency, and error rates. When sync breaks, we know within seconds.

Conclusion

Building real-time sync is challenging but rewarding. The key principles: separate ingestion from processing, track event origins to prevent loops, and build observability from day one.

Our Slack integration has become one of our most-used features. See the full Slack integration guide to get started in minutes.

Want to see what else you can automate? Read our post on how AI handles tickets automatically.

Share this post

Jordan

Jordan

Co-founder

Related posts

AI & automation

The Future of Freelancing: Let AI Handle Your Tickets

How AI-powered ticket automation is transforming the way freelancers and small teams manage client work, from request to resolution.

Jordan
Jordan1 Feb 2026 · 7 min read
Product updates

5 Hidden Features in Refront That Save Hours Every Week

Discover the lesser-known features in Refront that can dramatically reduce your admin time and boost productivity.

Jordan
Jordan28 Jan 2026 · 6 min read
Why we built Refront
Product updates

Why we built Refront

From frustrated entrepreneurs to 200% more revenue. This is our story and why we believe work can be different.

Jordan Munk
Jordan Munk25 Jan 2026 · 5 min
BestBank
getdonelist
MarketSavy
Globalchart
SlabSpace
EpicDev
dataBites.
ExDone
BestBank
getdonelist
MarketSavy
Globalchart
SlabSpace
EpicDev
dataBites.
ExDone

Features like collaboration,
code explainers and more

Whether you're part of a team or an individual, Refront helps you supercharge your code seamlessly.

Get startedStart for free

Refront is a workflow automation platform built to help teams turn work into solved tasks end to end.

© 2026 MG Software B.V. All rights reserved.

IntegrationsSlackGitHubAzure DevOpsStripeCursor
ResourcesKnowledge BaseComparisonsSolutionsTemplatesExamplesDirectoryLocationsTools
HomeFeaturesAbout UsContactPricingBlog