Troubleshooting
Common issues and solutions when using the MsGine SDK and API.
Authentication Issues
Error: "Invalid or missing API key"
Symptoms:
- HTTP 401 status code
- Error code:
unauthorized
Causes:
- API key is incorrect or missing
- Key has been revoked
- Key not properly included in request headers
Solutions:
Check Key Format
typescript
// ✅ Correct — SDK handles auth automatically
const client = new MsGineClient({
apiKey: process.env.MSGINE_API_KEY!,
})
// ✅ Correct format for manual requests
fetch('https://api.msgine.net/api/v1/developers/sms', {
headers: {
'X-Api-Key': process.env.MSGINE_API_KEY!,
}
})Verify Key
bash
curl https://api.msgine.net/api/v1/account \
-H "X-Api-Key: YOUR_API_KEY"If this fails, generate a new key in the Developer Dashboard.
Check Environment Variables
typescript
// Add debugging
console.log('API Key:', process.env.MSGINE_API_KEY ? 'Set' : 'Missing')
if (!process.env.MSGINE_API_KEY) {
throw new Error('MSGINE_API_KEY environment variable is not set')
}Error: "Insufficient permissions"
Symptoms:
- HTTP 403 status code
- Error code:
forbidden
Solutions:
- Generate new key: Create a key with appropriate permissions in the dashboard
- Contact support: If you need additional permissions
Phone Number Issues
Error: "Invalid phone number"
Symptoms:
- HTTP 400 status code
- Error code:
invalid_phone_number
Common Mistakes:
typescript
// ❌ Missing country code
to: '0701521269'
// ❌ Missing + prefix
to: '256701521269'
// ❌ Has spaces or dashes
to: '+256 701 521 269'
to: '+256-701-521-269'
// ✅ Correct E.164 format
to: '+256701521269'Solution:
typescript
function formatPhoneNumber(phone: string): string {
// Remove all non-digit characters except +
let cleaned = phone.replace(/[^\d+]/g, '')
// Add + if missing
if (!cleaned.startsWith('+')) {
throw new Error('Please include country code with + prefix')
}
return cleaned
}
// Usage
const phone = formatPhoneNumber('+256 701 521 269')
// Returns: '+256701521269'Messages Not Delivering
Symptoms:
- Message status remains
pendingor changes tofailed - No error during sending
Possible Causes:
- Invalid phone number: Number doesn't exist or is invalid
- Network issues: Carrier network problems
- Phone is off: Recipient's phone is turned off
- Blocked number: Number is on a blocklist
Solutions:
Use Webhooks for Status Updates
typescript
await client.sms.send({
to: '+256701521269',
message: 'Hello!',
callbackUrl: 'https://myapp.com/webhook'
})
// Webhook handler
app.post('/webhook', (req, res) => {
const { id, status, failureReason } = req.body
if (status === 'failed') {
console.log(`Message ${id} failed: ${failureReason}`)
}
res.status(200).send()
})Rate Limiting Issues
Error: "Rate limit exceeded"
Symptoms:
- HTTP 429 status code
- Error code:
rate_limit_exceeded
Solutions:
Implement Retry with Backoff
typescript
async function sendWithRetry(to: string, message: string, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await client.sms.send({ to, message })
} catch (error) {
if (
error instanceof MsGineError &&
error.code === 'rate_limit_exceeded' &&
attempt < maxRetries - 1
) {
const delay = Math.pow(2, attempt) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
} else {
throw error
}
}
}
}Use Request Queuing
typescript
class MessageQueue {
private queue: Array<() => Promise<any>> = []
private processing = false
private readonly delayMs = 600 // 100 requests/min = 600ms between requests
async enqueue<T>(fn: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await fn()
resolve(result)
} catch (error) {
reject(error)
}
})
if (!this.processing) {
this.process()
}
})
}
private async process() {
this.processing = true
while (this.queue.length > 0) {
const fn = this.queue.shift()!
await fn()
await new Promise(resolve => setTimeout(resolve, this.delayMs))
}
this.processing = false
}
}
// Usage
const queue = new MessageQueue()
for (const phone of phones) {
await queue.enqueue(() =>
client.sms.send({ to: phone, message: 'Hello!' })
)
}Check Rate Limit Headers
typescript
// When using REST API directly
const response = await fetch('https://api.msgine.net/api/v1/developers/sms', {
method: 'POST',
headers: {
'X-Api-Key': process.env.MSGINE_API_KEY!,
'Content-Type': 'application/json'
},
body: JSON.stringify({ to: phone, message: text })
})
const remaining = response.headers.get('X-RateLimit-Remaining')
const reset = response.headers.get('X-RateLimit-Reset')
console.log(`Remaining: ${remaining}`)
console.log(`Resets at: ${new Date(parseInt(reset!) * 1000)}`)Network and Timeout Issues
Error: "Network request failed"
Symptoms:
- Error code:
network_error - Request times out
Solutions:
Check Network Connectivity
bash
# Test connection to API
curl https://api.msgine.net/api/v1/account \
-H "X-Api-Key: YOUR_API_KEY"
# Check DNS resolution
nslookup api.msgine.net
# Check firewall rules
# Ensure outbound HTTPS (port 443) is allowedVerify Proxy Settings
typescript
// If using a proxy
import { HttpsProxyAgent } from 'https-proxy-agent'
const agent = new HttpsProxyAgent(process.env.HTTP_PROXY!)
// Configure your HTTP client to use the proxySDK Installation Issues
Error: "Cannot find module '@msgine/sdk'"
Solutions:
Reinstall the Package
bash
# Using pnpm
pnpm add @msgine/sdk
# Using npm
npm install @msgine/sdk
# Using yarn
yarn add @msgine/sdkCheck package.json
Ensure the package is listed in dependencies:
json
{
"dependencies": {
"@msgine/sdk": "^2.0.0"
}
}Clear Node Modules
bash
rm -rf node_modules package-lock.json
npm installTypeScript Type Errors
Solutions:
Ensure TypeScript Version
bash
npm install -D typescript@latestCheck tsconfig.json
json
{
"compilerOptions": {
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true
}
}Balance and Billing Issues
Error: "Insufficient balance"
Symptoms:
- HTTP 402 status code
- Error code:
insufficient_balance
Solutions:
- Add funds: Visit the Billing Dashboard
- Check pricing: SMS costs 30 UGX per message in Uganda
Monitor Balance
typescript
async function checkBalanceAndAlert() {
const account = await client.getAccount()
console.log(`Balance: ${account.balance} ${account.currency}`)
if (account.balance < 300) { // Less than 10 SMS worth
console.warn('Low balance! Please add funds.')
// Send alert
}
}
// Check before sending
await checkBalanceAndAlert()Webhook Issues
Webhooks Not Being Received
Symptoms:
- Webhook handler never called
- No POST requests to webhook URL
Solutions:
Verify Webhook URL
bash
# Test webhook URL is accessible
curl https://your-app.com/webhooks/msgineCheck HTTPS
MsGine only sends to HTTPS URLs:
typescript
// ❌ Will be rejected
callbackUrl: 'http://myapp.com/webhook'
// ✅ Correct
callbackUrl: 'https://myapp.com/webhook'Use ngrok for Local Testing
bash
# Install ngrok
npm install -g ngrok
# Start ngrok tunnel
ngrok http 3000
# Use ngrok URL as webhook URL
# https://abc123.ngrok.io/webhooks/msgineCheck Firewall
Ensure MsGine's IP addresses aren't blocked:
52.89.214.238
34.212.75.30
54.218.53.128Webhook Signature Verification Fails
Symptoms:
- Signature verification returns false
- Webhook requests rejected
Solutions:
Check Webhook Secret
typescript
// Verify you're using the correct webhook secret
const secret = process.env.MSGINE_WEBHOOK_SECRET!
console.log('Secret:', secret ? 'Set' : 'Missing')Use Raw Request Body
typescript
import express from 'express'
const app = express()
// Use raw body for signature verification
app.post(
'/webhooks/msgine',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-msgine-signature'] as string
const payload = req.body.toString()
const expectedSignature = crypto
.createHmac('sha256', process.env.MSGINE_WEBHOOK_SECRET!)
.update(payload)
.digest('hex')
if (signature === expectedSignature) {
// Signature valid
const event = JSON.parse(payload)
// Process event
}
res.status(200).send()
}
)Message Content Issues
Messages Appearing Garbled
Symptoms:
- Special characters display incorrectly
- Emojis show as question marks
Solutions:
Use UTF-8 Encoding
typescript
// Ensure UTF-8 encoding
const message = 'Hello! 👋'
await client.sms.send({
to: '+256701521269',
message
})Message Split into Multiple Parts
Cause: Message exceeds single SMS length (160 characters for GSM-7, 70 for Unicode)
Solution:
typescript
function estimateMessageParts(message: string): number {
const hasUnicode = /[^\x00-\x7F]/.test(message)
const singleLength = hasUnicode ? 70 : 160
const partLength = hasUnicode ? 67 : 153
if (message.length <= singleLength) {
return 1
}
return Math.ceil(message.length / partLength)
}
const message = 'Your long message here...'
const parts = estimateMessageParts(message)
console.log(`This message will be sent as ${parts} parts`)Getting More Help
Check API Status
Visit the Status Page to check for service incidents.
Contact Support
If you're still experiencing issues:
- Email: support@msgine.net
- Include:
- Error message
- Message ID (if applicable)
- Timestamp
- Code snippet (remove sensitive data)
- Response time: Usually within 24 hours
Next Steps
- Best Practices - Avoid common issues
- Error Handling - Handle errors gracefully
- Security Guide - Secure your integration