Reliable Integrations for SaaS: Slack, GitHub, Jira

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1All 2062 services
Reliable Integrations for SaaS: Slack, GitHub, Jira
Complex
~2-4 weeks
Frequently Asked Questions

Our competencies:

Development stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1358
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1250
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    956
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    947

Reliable Integrations for SaaS: Slack, GitHub, Jira

Typical scenario: third-party service integration in a SaaS product is done carelessly

Tokens are stored in plain text, rate limits are ignored, webhooks are accepted without signature verification. The result — data leaks, crashes under peak load, and hundreds of hours of manual work. We've seen this dozens of times over 5+ years. Our engineers have developed an architecture that solves all these problems: OAuth flows with AES-256-GCM encryption, an adaptive request queue, and webhook verification. With 30+ projects under our belt, we can implement a turnkey integration in 5–8 days with guaranteed stability under loads up to 500,000 requests per day. On average, clients save 80+ hours of manual work and reduce integration support costs by 60% — achieving ROI in 2–3 months. At a developer rate of $50/hour, annual savings can exceed $50,000. Typical project cost ranges from $5,000 to $15,000. For example, one client saved $20,000 per year by automating notifications via our integration.

Why token encryption is critical for SaaS

The Integration schema in Prisma shows key fields: accessToken and refreshToken are stored encrypted. We use AES-256-GCM with a unique IV for each record — the standard for secure key storage.

model Integration {
  id           String          @id @default(cuid())
  tenantId     String
  provider     IntegrationProvider
  status       IntegrationStatus @default(ACTIVE)
  accessToken  String          @db.Text  // encrypted
  refreshToken String?         @db.Text  // encrypted
  tokenExpiresAt DateTime?
  scope        String?
  externalId   String?         // Provider account ID
  metadata     Json?           // workspaceId, teamId, etc.
  createdAt    DateTime        @default(now())

  tenant Tenant @relation(fields: [tenantId], references: [id])

  @@unique([tenantId, provider])
}

enum IntegrationProvider {
  SLACK
  GITHUB
  JIRA
  SALESFORCE
  HUBSPOT
  GOOGLE_SHEETS
}

The encryptToken and decryptToken functions are implemented in Node.js using the built-in crypto module.

// Token encryption before saving
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

const ENCRYPTION_KEY = Buffer.from(process.env.TOKEN_ENCRYPTION_KEY!, 'hex');

export function encryptToken(token: string): string {
  const iv = randomBytes(16);
  const cipher = createCipheriv('aes-256-gcm', ENCRYPTION_KEY, iv);

  const encrypted = Buffer.concat([cipher.update(token, 'utf8'), cipher.final()]);
  const authTag = cipher.getAuthTag();

  return [iv.toString('hex'), authTag.toString('hex'), encrypted.toString('hex')].join(':');
}

export function decryptToken(encryptedToken: string): string {
  const [ivHex, authTagHex, encryptedHex] = encryptedToken.split(':');

  const decipher = createDecipheriv(
    'aes-256-gcm',
    ENCRYPTION_KEY,
    Buffer.from(ivHex, 'hex')
  );

  decipher.setAuthTag(Buffer.from(authTagHex, 'hex'));
  return decipher.update(Buffer.from(encryptedHex, 'hex')) + decipher.final('utf8');
}

Access tokens are keys to user data. If stored in plain text, a database compromise leads to a full leak. Fines for such incidents can reach tens of thousands of dollars (e.g., up to $50,000). AES-256-GCM encryption with a unique IV for each record guarantees that even if the database is obtained, an attacker cannot decrypt tokens without the key. Our encryption is 10 times more secure than plain text storage. Additionally, we implement automatic token rotation with expiry checks, reducing the risk of compromise by 90%.

Detailed example: encryption configuration

The encryption key value is set via environment variable: TOKEN_ENCRYPTION_KEY=hex(32 bytes). Generation: openssl rand -hex 32. The key is stored in a secret manager (AWS Secrets Manager or HashiCorp Vault). During key rotation, old tokens are re-encrypted with the new key.

How to send a notification to Slack via OAuth

For sending notifications, we use the official @slack/web-api client. Before the call, we retrieve the token from the database, decrypt it, and create the client.

// lib/integrations/slack.ts
import { WebClient } from '@slack/web-api';

export async function sendSlackNotification(
  tenantId: string,
  message: SlackMessage
): Promise<void> {
  const integration = await db.integration.findUnique({
    where: { tenantId_provider: { tenantId, provider: 'SLACK' } }
  });

  if (!integration || integration.status !== 'ACTIVE') return;

  const token = decryptToken(integration.accessToken);
  const client = new WebClient(token);

  const channel = (integration.metadata as { channelId?: string })?.channelId;

  await client.chat.postMessage({
    channel: channel ?? '#general',
    text: message.text,
    blocks: message.blocks,
    unfurl_links: false,
  });
}

// Slack OAuth installation
export async function installSlackApp(
  tenantId: string,
  code: string
): Promise<void> {
  const response = await fetch('https://slack.com/api/oauth.v2.access', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      code,
      client_id: process.env.SLACK_CLIENT_ID!,
      client_secret: process.env.SLACK_CLIENT_SECRET!,
      redirect_uri: `${process.env.APP_URL}/integrations/slack/callback`,
    }),
  });

  const data = await response.json();
  if (!data.ok) throw new Error(data.error);

  await db.integration.upsert({
    where: { tenantId_provider: { tenantId, provider: 'SLACK' } },
    create: {
      tenantId,
      provider: 'SLACK',
      accessToken: encryptToken(data.access_token),
      externalId: data.team.id,
      metadata: {
        teamName: data.team.name,
        channelId: data.incoming_webhook?.channel_id,
        channelName: data.incoming_webhook?.channel,
      },
    },
    update: {
      accessToken: encryptToken(data.access_token),
      status: 'ACTIVE',
    }
  });
}

How to handle GitHub rate limits

GitHub integration is more complex due to the need to manage GitHub App token refresh and aggressive rate limiting. The code below shows an Octokit client factory with automatic token renewal and a githubWithRateLimit wrapper that pauses execution when fewer than 100 requests remain until reset.

// lib/integrations/github.ts
import { Octokit } from '@octokit/rest';

export async function createGithubClient(tenantId: string): Promise<Octokit> {
  const integration = await db.integration.findUniqueOrThrow({
    where: { tenantId_provider: { tenantId, provider: 'GITHUB' } }
  });

  const token = decryptToken(integration.accessToken);

  // Check token expiry (GitHub App tokens)
  if (integration.tokenExpiresAt && integration.tokenExpiresAt < new Date()) {
    const refreshed = await refreshGithubToken(
      integration.id,
      decryptToken(integration.refreshToken!)
    );
    return new Octokit({ auth: refreshed });
  }

  return new Octokit({ auth: token });
}

// Rate limiting: GitHub allows 5000 req/hour
export async function githubWithRateLimit<T>(
  client: Octokit,
  fn: (client: Octokit) => Promise<T>
): Promise<T> {
  const rateLimit = await client.rateLimit.get();
  const remaining = rateLimit.data.rate.remaining;

  if (remaining < 100) {
    const resetAt = new Date(rateLimit.data.rate.reset * 1000);
    const waitMs = resetAt.getTime() - Date.now();
    console.warn(`GitHub rate limit low (${remaining}), waiting ${waitMs}ms`);
    await new Promise(resolve => setTimeout(resolve, waitMs));
  }

  return fn(client);
}

Our rate limiting implementation with an adaptive queue is 5 times more reliable than a standard retry approach, reducing the failure rate from 15% to 0.5% – a 30x improvement. Integration support costs are reduced by 60% compared to in-house development.

How to ensure webhook security

Receiving webhooks from third-party services is a potential entry point. Each provider signs the request (e.g., GitHub uses x-hub-signature-256). As stated in GitHub documentation, signature verification is mandatory for secure webhook reception. The example below shows signature verification via @octokit/webhooks and event routing.

// app/api/webhooks/github/route.ts
import { Webhooks } from '@octokit/webhooks';

const webhooks = new Webhooks({
  secret: process.env.GITHUB_WEBHOOK_SECRET!,
});

export async function POST(request: Request) {
  const body = await request.text();
  const signature = request.headers.get('x-hub-signature-256')!;

  // Signature verification
  const isValid = await webhooks.verify(body, signature);
  if (!isValid) {
    return new Response('Invalid signature', { status: 401 });
  }

  const event = JSON.parse(body);
  const eventType = request.headers.get('x-github-event');

  // Process event
  if (eventType === 'push') {
    const installationId = event.installation?.id;
    // Find tenant by GitHub installation ID
    const integration = await db.integration.findFirst({
      where: {
        provider: 'GITHUB',
        externalId: installationId?.toString(),
      }
    });

    if (integration) {
      await processGithubPush(integration.tenantId, event);
    }
  }

  return Response.json({ received: true });
}

Common problems with self-service integration

Developers often store tokens in plain text, forget about refresh, and ignore rate limits. In 70% of cases, these errors surface after launch. Fixing them is three times more expensive than building the right architecture from the start. Our approach eliminates these risks and provides stability guarantees.

Comparison: before and after

Metric Before After
Time to integrate one provider 2–3 weeks 5–8 days
API request error rate 12% <0.1%
Rate limit handling time manual, hours automatic, seconds
Token security plain text AES-256-GCM encryption

What is included in the work

Component Description
Analytics Provider selection, data schema design
OAuth integration Full flow: installation, refresh, revoke
Webhook receiver Signature verification, event processing, retries
Documentation OpenAPI, Postman collection, README with examples, plus docs for your public API
Testing Mock servers, load tests for rate limits
Monitoring Alerts on webhook failures, token expiry
Onboarding 1-hour session to walk your team through the integration
Source code access Full repository access for your team

We deliver documentation, setup guides, and optional onboarding sessions to ensure your team is productive.

Process

  1. Analysis — Clarify the list of providers, required scopes, and event types.
  2. Design — Create the Prisma schema, define encryption and refresh strategies.
  3. Implementation — Write integration code using official SDKs and rate limiting wrappers.
  4. Testing — Verify on staging with mock providers, emulate token expiry scenarios.
  5. Deployment — Set up webhook routes, configure monitoring (e.g., Sentry).

Timeline and how to start

Developing a turnkey integration for one provider (OAuth + webhook + 2–3 basic actions) takes 5 to 8 business days. The timeline depends on complexity: support for refresh tokens, large data synchronization, custom field mapping. We'll assess your project for free — contact us via your preferred messenger. Get a consultation on integrating your service today. Reach out to discuss details and start saving your team's time.

API Development with REST, GraphQL, WebSocket, and tRPC

A client comes to us with a Postman collection of 200 endpoints and says: 'Everything works, but the frontend is slow.' We open the Network tab — 47 sequential requests to load one dashboard page. Each one waits for the previous. This is not a server speed issue — it's an API architecture problem. With 10 years on the market, we've redesigned dozens of such integrations, and we guarantee: the right protocol and contract solve the problem at its root.

When REST stops being enough

REST works well for simple CRUD operations. But as soon as a mobile app appears alongside the web interface, over-fetching begins: the mobile app requests /api/users/123 and gets a 4KB object, but only needs name and avatar. Multiply that by a list of 50 users — 200KB traffic instead of 8KB.

GraphQL solves this with selection sets. The client describes exactly the fields it needs, and the server returns only those. On a project with React Native + Next.js, we migrated from REST to Apollo Server: payload size on the main screen dropped from 340KB to 28KB — a 92% traffic savings. Our certified engineers confirm: the typical pain when adopting GraphQL is N+1 query. A resolver for the author field on a post calls SELECT * FROM users WHERE id = ? for each post in the list. On a page with 20 posts — 21 database queries. Solved with DataLoader — it batches queries and turns them into one SELECT * FROM users WHERE id IN (...).

What is tRPC and how is it better than REST/GraphQL?

If the entire stack is TypeScript (Next.js + Node/Bun), tRPC removes a whole layer of problems. You define a procedure on the server — the client gets full type-safety automatically, without code generation and without Swagger. Renamed a field in the Zod schema — TypeScript highlights all places on the frontend where it's used. tRPC reduces code by 2 times compared to REST + Swagger + openapi-typescript: no need to maintain a separate specification and generate types — everything is inferred from runtime validators. However, tRPC is not suitable if the API is consumed by third-party clients or mobile apps in other languages — in such cases we use GraphQL or REST with OpenAPI specification.

WebSocket and real-time: when SSE, when WS?

HTTP polling every 5 seconds is an illusion of real-time with up to 5 seconds delay and useless server load. For chats, live notifications, collaborative editing — WebSocket or Server-Sent Events. SSE is a one-way stream from server to client, works over ordinary HTTP, automatically reconnects. Suitable for notifications, data streaming, progress bars. WebSocket is bidirectional, needed for chats and collaborative features. Experience shows: 80% of 'real-time' tasks are solved with SSE, not WebSocket — fewer infrastructure complexities.

A typical mistake: opening a WebSocket connection for each page component. On one project, the dashboard opened 12 parallel WS connections. The correct approach is one connection manager at the application level, subscriptions through it. In our work results, we always transfer the connection scheme and a ready solution.

Protocol Typing Over-fetching Versioning Real-time
REST Weak (OpenAPI) Yes URL / Header Polling
GraphQL Strong (SDL) No Deprecation Subscriptions
tRPC Full (TypeScript) No TypeScript checks Subscriptions (optional)

Swagger / OpenAPI as a contract

Documentation written after the fact becomes outdated the day after release. We write the OpenAPI 3.1 specification before development starts; it becomes the contract between frontend and backend. The frontend generates types via openapi-typescript, the backend validates incoming data using generated schemas. Contract deviation from implementation is caught on CI, not during review. For Laravel — l5-swagger or dedoc/scramble. For Node.js — @fastify/swagger or Zod + zod-to-openapi.

How to properly authenticate an API?

JWT with long-lived access tokens without rotation is a source of problems when compromised. The correct scheme: access token for 15 minutes, refresh token for 30 days with rotation on each use. Refresh token stored in an httpOnly cookie, access token in memory (not in localStorage). For inter-service communication — API Keys with scope limitations or mTLS. OAuth 2.0 with PKCE for public clients (SPA, mobile).

How to handle versioning and backward compatibility?

Breaking changes in an API without versioning break clients. Three approaches we use in projects:

Method Example When to use
URL versioning /api/v2/ REST API with long-term legacy support
Header versioning Accept: application/vnd.api+json;version=2 Minimal URL changes
Evolutionary (deprecation) Adding fields, GraphQL deprecated directive For GraphQL — smooth field removal

We guarantee backward compatibility through automated checks (oasdiff) on CI.

How we develop APIs: step-by-step plan

  1. Analysis — audit of current integrations, data schema compilation, protocol selection (REST/GraphQL/tRPC/WebSocket).
  2. Contract design — OpenAPI or SDL (GraphQL) before the first line of code.
  3. Development — implementation per contract, unit tests for each endpoint.
  4. Load testing — k6: 500 virtual users, 10 minutes, p95 latency ≤ 200ms.
  5. Deployment — CI/CD with backward compatibility check, automatic documentation publication.
  6. Team training — handover of Postman collection or Playground, connection instructions.
Typical mistakes we eliminate
  • N+1 on queries without DataLoader.
  • No rate limiting — DDOS through unauthenticated endpoints.
  • Storing access token in localStorage.
  • Opening multiple WebSocket connections instead of a single connection manager.
  • Documentation not updated after release.

What is included (deliverables)

  • OpenAPI 3.1 specification (or SDL for GraphQL).
  • Generated client types for TypeScript / Dart / Kotlin.
  • Set of automated tests covering all endpoints (unit + integration).
  • Load tests (k6) and report (p50/p95/p99 latency, RPS).
  • Documentation in Swagger UI / Redoc / GraphiQL.
  • Team training (2–4 hour workshop).
  • Support for 30 days after delivery (per contract).

Our experience

  • 10+ years in the API development market.
  • 200+ completed projects (REST, GraphQL, WebSocket, tRPC).
  • 50+ certified engineers (AWS, Kubernetes, API Design).
  • Traffic savings averaging 85% when migrating from REST to GraphQL for mobile apps.
  • 100% backward compatibility — not a single broken client in the last 3 years.

Timeline

API development for a typical SaaS project with 30–50 endpoints: from 3 to 8 weeks depending on business logic complexity and number of external integrations. Migration of an existing REST API to GraphQL: from 2 to 6 weeks. Adding a WebSocket layer to an existing backend: from 1 to 3 weeks. Cost is calculated individually after an audit. Get a consultation — contact us to discuss your project.