> ## Documentation Index
> Fetch the complete documentation index at: https://dashtray.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Best Practices

> Recommendations for using Dashtray effectively

## General

### Keep URLs Private

Your webhook URLs are like passwords.

❌ **Don't:**

* Commit to git repositories
* Paste in Slack/Discord
* Share via email
* Post in public forums

✅ **Do:**

* Store in environment variables
* Use secrets manager (1Password, AWS Secrets, etc.)
* Only share with trusted teammates
* Rotate if accidentally exposed

```bash theme={null}
# Good: Store in .env
DASHTRAY_WEBHOOK=https://dashtray.app/p/abc1234

# Bad: Hardcoded in code
const webhook = "https://dashtray.app/p/abc1234";
```

### Generate Unique API Keys Per Integration

Don't reuse the same API key everywhere.

✅ **Better approach:**

* API key for GitHub → `github-ci`
* API key for Stripe → `stripe-payments`
* API key for Zapier → `zapier-automations`

**Benefits:**

* Revoke one without breaking others
* Track which integration is sending requests
* Easier to audit and rotate keys

### Implement Retry Logic

Not all requests will succeed first try.

```javascript theme={null}
async function sendWithRetry(data, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch('/api/v1/notifications/send', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      });
      
      if (response.ok) return response;
      if (response.status === 429) {
        // Rate limited - back off
        await new Promise(r => setTimeout(r, 60000));
        continue;
      }
      if (response.status >= 500) {
        // Server error - retry
        await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
        continue;
      }
      throw new Error(`Request failed: ${response.status}`);
    } catch (err) {
      if (i === maxRetries - 1) throw err;
      await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
    }
  }
}
```

### Catch and Log Errors

Always handle failures gracefully.

```javascript theme={null}
try {
  await sendNotification({
    title: "Build completed",
    description: `Status: ${buildStatus}`
  });
} catch (err) {
  // Log the error but don't crash
  console.error('Failed to send notification:', err);
  // Continue with rest of workflow
}
```

***

## Webhooks

### Use Template Variables

Don't duplicate data in title and body.

❌ **Don't:**

```
Title: "Payment from john@example.com - $99.00"
Body: "Payment from john@example.com - $99.00"
```

✅ **Do:**

```
Title: "💳 Payment Received"
Body: "${{amount}} from {{email}}"
```

### Test Webhooks Before Production

Most services let you send test webhooks.

1. Send test event from service
2. Check it appears in Dashtray history
3. Verify data is correct
4. Configure template
5. Then enable in production

### Set Appropriate Frequency

Don't notify for every single event.

❌ **Too noisy:**

* Every page view
* Every log line
* Every API call

✅ **Good frequency:**

* Errors/failures
* Completed tasks
* State changes
* Important milestones

### Handle High-Volume Services

If service sends many events:

**Option 1: Filter at source**

* Only send certain events
* Use service's filters/rules
* Reduce payload size

**Option 2: Multiple alerts**

* Create separate alerts for different triggers
* Route based on event type
* Better organization

**Option 3: Batch notifications**

* Collect events, send summary
* Example: "5 deployments completed"
* Less noisy

### Protect Your Webhook URL

The webhook URL itself is the credential — anyone with it can send to your phone. Treat it like a password:

* **Keep it secret** - Don't commit it to repos or share it publicly
* **Rotate on exposure** - If a URL leaks, rotate it from the alert page; the old code stops working immediately
* **The code is the auth** - There is no separate signing secret; the unguessable short code protects the endpoint

### Respond Quickly

Acknowledge the webhook immediately, process async:

```javascript theme={null}
app.post('/webhook', async (req, res) => {
  // Return immediately
  res.json({ ok: true });
  
  // Process in background
  processWebhookAsync(req.body);
});
```

***

## Rate Limiting

### Plan Ahead

Know your plan's limits:

| Plan | API | Webhooks |
| - | - | - |
| Starter | 100/min | 60/min per alert |
| Pro | 1,000/min | 60/min per alert |

### Monitor Usage

Check your stats regularly:

* Settings → API Stats
* See requests per minute
* Watch for approaching limits

### Handle Rate Limits Gracefully

```javascript theme={null}
if (response.status === 429) {
  const retryAfter = response.headers.get('Retry-After') || 60;
  console.log(`Rate limited. Retry after ${retryAfter} seconds`);
  
  // Exponential backoff
  await sleep(retryAfter * 1000);
  return retry();
}
```

### Optimize Requests

Avoid unnecessary calls:

❌ **Inefficient:**

* Calling API for every event
* Sending duplicate notifications
* Polling instead of webhooks

✅ **Efficient:**

* Batch similar notifications
* Use webhooks (they push to you)
* Only send when necessary

***

## Security

### Rotate Keys Regularly

Best practice: rotate API keys quarterly

**Steps:**

1. Generate new API key
2. Update all places using old key
3. Test everything works
4. Revoke old key

### Never Log API Keys

```javascript theme={null}
// Bad - logs key to console
console.log('Using API key:', apiKey);

// Good - only log prefix
console.log('Using API key:', apiKey.slice(0, 8) + '...');
```

### Use Environment Variables

```bash theme={null}
# .env
DASHTRAY_API_KEY=sk_test_...

# Never hardcode:
const apiKey = "sk_test_..."; // ❌ Bad
const apiKey = process.env.DASHTRAY_API_KEY; // ✅ Good
```

### Validate Input

Always sanitize webhook payloads:

```javascript theme={null}
function validateNotification(data) {
  if (!data.title || typeof data.title !== 'string') {
    throw new Error('Invalid title');
  }
  if (!data.description || typeof data.description !== 'string') {
    throw new Error('Invalid description');
  }
  if (data.title.length > 200) {
    throw new Error('Title too long');
  }
  return true;
}
```

***

## Performance

### Batch Notifications

Don't send 100 individual notifications. Send summaries:

❌ **100 separate requests:**

```
Deploy started
Deploy in progress (1/10)
Deploy in progress (2/10)
...
Deploy completed
```

✅ **One request:**

```
Deploy completed: 10 services deployed in 5 minutes
```

### Cache Results

Don't recalculate if you already sent:

```javascript theme={null}
const cache = {};

async function sendUniqueNotification(key, data) {
  if (cache[key]) {
    console.log('Already notified about this');
    return;
  }
  
  await sendNotification(data);
  cache[key] = true;
}
```

### Don't Spam in Quiet Hours

Respect user's quiet hours setting:

```javascript theme={null}
const now = new Date();
const hour = now.getHours();

if (hour >= 22 || hour < 8) {
  console.log('Quiet hours - deferring notification');
  // Queue for morning
} else {
  await sendNotification(data);
}
```

***

## Monitoring

### Track Delivery

Monitor successful vs failed notifications:

```javascript theme={null}
const results = {
  sent: 0,
  failed: 0,
  rateLimited: 0
};

async function sendWithTracking(data) {
  try {
    const response = await sendNotification(data);
    results.sent++;
  } catch (err) {
    if (err.statusCode === 429) {
      results.rateLimited++;
    } else {
      results.failed++;
    }
  }
}
```

### Alert on Failures

Set up alerts if notification delivery fails:

```javascript theme={null}
if (results.failed > 10) {
  // Alert engineering team
  console.error('High failure rate detected');
  // Page on-call engineer
}
```

### Use Structured Logging

Log in structured format for analysis:

```javascript theme={null}
console.log(JSON.stringify({
  timestamp: new Date().toISOString(),
  event: 'notification_sent',
  title: data.title,
  status: 'success',
  latencyMs: responseTime
}));
```

***

## Common Pitfalls to Avoid

❌ **Mistake: Blocking on notification**

```javascript theme={null}
// Bad - if notification fails, entire operation fails
await sendNotification(data);
completeTask();
```

✅ **Better: Fire and forget**

```javascript theme={null}
// Good - complete task, notify async
completeTask();
sendNotification(data).catch(err => {
  logger.error('Notification failed:', err);
});
```

***

❌ **Mistake: Hardcoding tokens**

```javascript theme={null}
const token = "sk_test_abc123";
```

✅ **Better: Use environment variables**

```javascript theme={null}
const token = process.env.DASHTRAY_API_KEY;
```

***

❌ **Mistake: No error handling**

```javascript theme={null}
await sendNotification(data);
```

✅ **Better: Catch errors**

```javascript theme={null}
try {
  await sendNotification(data);
} catch (err) {
  logger.error('Failed to send notification:', err);
}
```

***

## Summary Checklist

Before going to production:

* ✅ Validate all inputs
* ✅ Implement retry logic
* ✅ Use unique API keys
* ✅ Store secrets in environment variables
* ✅ Verify webhook signatures
* ✅ Log errors but not secrets
* ✅ Test with real data
* ✅ Monitor delivery rates
* ✅ Handle rate limits gracefully
* ✅ Respect quiet hours

Ready to deploy! 🚀


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.