Home

BugTape docs

SaaS Integration Guide

Wire BugTape into a multi-tenant product.

How to integrate BugTape as a vendor into your B2B web application.

This guide is for teams building a SaaS product who want to use BugTape for bug reporting, either as the primary system or alongside an existing internal pipeline.

Architecture overview

┌─────────────────────────────────────────────────────┐
│  Your SaaS App (browser)                            │
│                                                     │
│  ┌──────────────┐   ┌────────────────────────────┐  │
│  │ BugTape SDK  │──▶│ BugTape API (SaaS)         │  │
│  │ (auto-capture│   │ POST /v1/ingest            │  │
│  │  + widget)   │   │ X-BugTape-Key: bt_live_xxx │  │
│  └──────────────┘   └────────────────────────────┘  │
│                              │                      │
│                              ▼                      │
│                     ┌────────────────┐              │
│                     │ BugTape Console│              │
│                     │ (bug inbox,    │              │
│                     │  AI analysis)  │              │
│                     └────────────────┘              │
│                              │                      │
│                     Webhook ─┘                      │
│                              ▼                      │
│                     ┌────────────────┐              │
│                     │ Your Backend   │              │
│                     │ (sync-back)    │              │
│                     └────────────────┘              │
└─────────────────────────────────────────────────────┘

Integrating BugTape into a SaaS web app

1. Create a BugTape project per environment

Create separate projects for each of your environments:

BugTape ProjectAPI KeyPurpose
myapp-productionbt_live_xxxProduction bug reports
myapp-stagingbt_live_yyyStaging/QA reports
myapp-developmentbt_test_zzzLocal dev testing

2. Initialize once with the selected project key

Use the hosted module in a browser entry point after applying your app's consent policy. The public npm package is not currently published. Select the environment's capture key in your application configuration.

import { init } from 'https://app.bugtape.ai/bugtape.mjs';

const bt = init({
  apiKey: 'YOUR_ENVIRONMENT_PROJECT_KEY',
  release: 'YOUR_RELEASE_ID',
  context: {
    userId: currentUser.id,
    environment: 'staging',
    metadata: { tenantId: currentOrg.id },
  },
});
// On the owning browser surface's teardown: bt.destroy();

For Next.js use the hosted module example. Keep initialization outside server execution and avoid initializing twice.

3. Keep tenant authorization on your server

Context fields label an occurrence; they are not an authorization boundary. A browser can change its submitted tenant ID. Use BugTape organization membership and project grants for access control. If your customers need separate console access, provision the appropriate organization/project boundary; projects alone do not isolate members of the same organization.

4. Set up webhooks for sync-back

Create a generic webhook integration in BugTape to receive notifications when bugs are filed:

POST /v1/integrations
{
  "projectId": "your-project-uuid",
  "type": "webhook",
  "name": "Sync to internal system",
  "config": {
    "url": "https://your-app.com/api/webhooks/bugtape",
    "secret": "your-hmac-secret"
  },
  "events": ["new_bug", "regression", "status_change"]
}

Your webhook handler receives:

{
  "event": "new_bug",
  "title": "Checkout fails on Safari",
  "severity": "critical",
  "url": "https://app.example.com/checkout",
  "bugId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": "2026-02-28T12:00:00Z"
}

Verify the HMAC signature:

import crypto from 'crypto';

function verifyBugTapeSignature(body: string, signature: string, secret: string): boolean {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex');
  if (typeof signature !== 'string') return false;
  const received = Buffer.from(signature);
  const wanted = Buffer.from(expected);
  return received.length === wanted.length && crypto.timingSafeEqual(received, wanted);
}

// In your webhook handler:
const sig = req.headers['x-bugtape-signature'] as string;
if (!verifyBugTapeSignature(rawBody, sig, process.env.BUGTAPE_WEBHOOK_SECRET!)) {
  return res.status(401).json({ error: 'Invalid signature' });
}

Dual-write migration from internal bug reporting

If you have an existing internal bug reporting system and want to migrate to BugTape gradually:

Phase 1: Dual-write (both systems receive reports)

async function reportBug(report: BugReport) {
  // Write to both systems in parallel
  const [bugtapeResult, internalResult] = await Promise.allSettled([
    submitToBugTape(report),
    submitToInternalSystem(report),
  ]);

  // Log any failures but don't block the user
  if (bugtapeResult.status === 'rejected') {
    console.warn('BugTape submission failed:', bugtapeResult.reason);
  }
  if (internalResult.status === 'rejected') {
    console.warn('Internal submission failed:', internalResult.reason);
  }
}

Phase 2: BugTape primary, internal fallback

async function reportBug(report: BugReport) {
  try {
    const result = await submitToBugTape(report);
    // Optionally sync the BugTape ID to your internal system
    await syncBugTapeId(result.id, report.internalRef);
    return result;
  } catch (error) {
    // Fall back to internal system
    console.warn('BugTape failed, falling back to internal:', error);
    return submitToInternalSystem(report);
  }
}

Phase 3: BugTape only

Remove the internal system. Use BugTape webhooks to sync relevant data back to your app.

How to avoid leaking secrets in frontend apps

What is browser-safe

ItemBrowser-safe?Notes
bt_live_* API keyYesIngest-only, cannot read data
bt_test_* API keyYesSame permissions as live
AI provider keyNoKeep on the server; omit openaiKey from browser configuration
encryptionKey optionUnsupportedEnd-to-end report storage/read/decryption is unavailable
JWT tokensNoManagement API only, never expose to end users
Webhook secretsNoServer-side only

Recommendations

  1. Store API keys in environment variables, not in source code. Use NEXT_PUBLIC_* prefix for browser-exposed values.
  1. Never expose JWT tokens to your end users. JWT tokens grant management access (read bugs, update status, manage team). They are for your team's console only.
  1. Keep AI provider keys server-side. Omit deprecated client-side provider configuration; set summarize: false to disable the summary request.
  1. Consider a server proxy if your security policy prohibits any API keys in client JavaScript. See integration and proxy guidance.
  1. Do not configure encryptionKey. The SDK rejects this unsupported path; it does not provide end-to-end encrypted report storage.

How to tag tenants, projects, and customers safely

Push a context event into the buffer at initialization:

bt.buffer.push({
  type: 'context',
  timestamp: Date.now(),
  data: {
    tenantId: 'org_abc123',
    tenantName: 'Acme Corp',     // OK — this is your customer's name
    userId: 'usr_xyz789',
    userRole: 'admin',
    release: 'v2.4.1',
    environment: 'production',
  },
});

This event is included while it remains in the bounded buffer. Use initialization context for fields that must accompany each occurrence.

Approach B: Reporter email

Include the user's email for attribution:

await fetch('https://app.bugtape.ai/v1/ingest', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-BugTape-Key': 'bt_live_xxx',
  },
  body: JSON.stringify({
    title: 'Report title',
    reporterEmail: user.email,
    events: bt.getEvents(),
  }),
});

Approach C: Separate BugTape projects per customer

A project per customer separates capture keys and report grouping. It does not by itself isolate console access within an organization. Use organization membership and project grants to enforce the access boundary you need.

What NOT to include in events

  • Passwords or credentials
  • Full credit card numbers (PII scrubbing catches these, but don't rely on it)
  • Internal service tokens
  • Database connection strings

The SDK's PII scrubber catches common patterns (emails, credit cards, SSNs, JWTs, API keys), but it is a safety net, not a guarantee. Avoid putting secrets into console.log or network request bodies.

The BugTape console supports direct links to reports:

https://app.bugtape.ai/console/issues/<bug-id>

The bug-id is the UUID returned by the ingest API (response.id).

When your webhook handler receives a bugId, construct a link:

function bugTapeUrl(bugId: string): string {
  return `https://app.bugtape.ai/console/issues/${bugId}`;
}

Show a "View in BugTape" link in your internal dashboard:

<a href="https://app.bugtape.ai/console/issues/a1b2c3d4" target="_blank">
  View in BugTape
</a>

Search and filter conventions

The BugTape console supports filtering by:

FilterDescription
Statusopen, in_progress, resolved, closed
Severitycritical, high, medium, low
ProjectFilter by project within org
SearchFull-text search in title and description
Sortnewest, oldest, severity, reports

These same filters are available via the management API (GET /v1/bugs?status=open&severity=critical&search=checkout).

Polling for sync-back

If webhooks are not feasible, poll the BugTape API:

// Poll every 60 seconds for new bugs
async function pollBugTape(since: string) {
  const response = await fetch(
    `https://app.bugtape.ai/v1/bugs?sort=newest&limit=20`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'X-BugTape-Org': orgId,
      },
    }
  );
  const { bugs } = await response.json();
  return bugs.filter((b: any) => b.created_at > since);
}

Recommendation: Prefer signed webhooks when your server can receive them. Delivery is asynchronous and subject to destination rules and retries; see agent delivery.