SDK Troubleshooting

Resolve common issues with Sent LogoSent SDKs. This guide covers frequent errors, debugging strategies, and solutions organized by symptom.

Quick Diagnostics

Before diving into specific issues, check these common causes:

API Key

Verify your API key is correct and not expired

Environment

Check if you're using the right environment (test vs production)

Rate Limits

You may have hit the rate limit - check retry-after headers

Template Status

WhatsApp templates must be APPROVED to send

Common Error Codes

Authentication Errors

401 Unauthorized - Invalid API key

Symptoms:

AuthenticationError: 401 - Invalid API key

Solutions:

  1. Verify your API key from the Sent Dashboard
  2. Check for extra whitespace or copy-paste errors
  3. Ensure you're using the right environment variable (SENT_DM_API_KEY)
  4. Verify the key hasn't been revoked
// Debug: Log first 8 characters
console.log('API Key:', process.env.SENT_DM_API_KEY?.substring(0, 8) + '...');

Contact Errors

404 Not Found - Contact not found

Symptoms:

NotFoundError: 404 - Contact not found

Solutions:

Contact opted out

Symptoms:

BadRequestError: 400 - Contact has opted out of messaging

Solution: The recipient has opted out. Respect their preference and don't retry.

try {
  const response = await client.messages.send({
    to: ['+1234567890'],
    template: {
      id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
      name: 'welcome'
    }
  });
} catch (error) {
  if (error instanceof SentDm.BadRequestError) {
    if (error.message.includes('opted out')) {
      // Update your database
      await db.users.update({
        where: { phone: '+1234567890' },
        data: { messagingOptOut: true }
      });

      // Don't retry - respect opt-out
      console.log('Contact opted out - skipping');
    }
  }
}
from sent_dm import BadRequestError

try:
    response = client.messages.send(
        to=['+1234567890'],
        template={
            'id': '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
            'name': 'welcome'
        }
    )
except BadRequestError as e:
    if 'opted out' in str(e):
        # Update your database
        db.users.update(
            phone='+1234567890',
            messaging_opt_out=True
        )

        # Don't retry - respect opt-out
        print('Contact opted out - skipping')

Template Errors

400 Bad Request - Template not found

Symptoms:

BadRequestError: 400 - Template not found

Solutions:

400 Bad Request - WhatsApp template pending

Symptoms:

BadRequestError: 400 - Template is not approved for WhatsApp

Solutions:

  1. Check template status in dashboard

    • WhatsApp templates need Meta approval (can take hours)
    • SMS templates work immediately
  2. Use SMS as fallback

// Try sending (may throw if template not approved)
try {
  const response = await client.messages.send({
    to: ['+1234567890'],
    template: {
      id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
      name: 'welcome'
    }
  });

  console.log('Sent:', response.data.messages[0].id);
} catch (error) {
  if (error instanceof SentDm.BadRequestError &&
      error.message.includes('not approved')) {
    console.log('Template not approved yet');

    // Queue for later or use alternative channel
    await db.queuedMessages.create({
      phoneNumber: '+1234567890',
      templateId: 'welcome-template',
      retryAfter: new Date(Date.now() + 3600000), // 1 hour
      status: 'pending_approval'
    });
  }
}
from sent_dm import BadRequestError

# Try sending (may raise if template not approved)
try:
    response = client.messages.send(
        to=['+1234567890'],
        template={
            'id': '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
            'name': 'welcome'
        }
    )

    print(f'Sent: {response.data.messages[0].id}')
except BadRequestError as e:
    if 'not approved' in str(e):
        print('Template not approved yet')

        # Queue for later or use alternative channel
        db.queued_messages.create(
            phone_number='+1234567890',
            template_id='welcome-template',
            retry_after=datetime.now() + timedelta(hours=1),
            status='pending_approval'
        )

Rate Limiting

429 Rate Limit - Too many requests

Symptoms:

RateLimitError: 429 - Rate limit exceeded
Retry-After: 60

Solutions:


Billing Errors

Payment Required - Account balance too low

Symptoms:

BadRequestError: 400 - Insufficient credits to send message

Solutions:

  1. Add credits in the dashboard

  2. Graceful degradation

try {
  const response = await client.messages.send({
    to: ['+1234567890'],
    template: {
      id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
      name: 'welcome'
    }
  });
} catch (error) {
  if (error instanceof SentDm.BadRequestError &&
      error.message.includes('credits')) {
    // Queue for later
    await db.messageQueue.create({
      ...messageData,
      status: 'pending_credits'
    });

    await notifyOpsTeam('Account balance low');
  }
}
from sent_dm import BadRequestError

try:
    response = client.messages.send(
        to=['+1234567890'],
        template={
            'id': '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
            'name': 'welcome'
        }
    )
except BadRequestError as e:
    if 'credits' in str(e):
        # Queue for later
        db.message_queue.create(
            status='pending_credits',
            **message_data
        )

        notify_ops_team('Account balance low')

Webhook Issues

Webhook not receiving events

Duplicate webhook events

Webhook events may be delivered multiple times. Handle them idempotently:

async function handleWebhook(event: WebhookEvent) {
  const eventId = event.meta?.request_id || event.id;

  // Check if already processed
  const existing = await db.processedEvents.findUnique({
    where: { eventId }
  });

  if (existing) {
    console.log(`Event ${eventId} already processed`);
    return { received: true };
  }

  // Process event...

  // Mark as processed
  await db.processedEvents.create({
    data: { eventId, processedAt: new Date() }
  });
}

Connection Issues

Timeout errors

Symptoms:

APIConnectionError: Request timeout after 30000ms

Solutions:

  1. Increase timeout
const client = new SentDm({
  timeout: 60000  // 60 seconds
});
from sent_dm import SentDm

client = SentDm(timeout=60.0)  # 60 seconds
client := sentdm.NewClient(
    option.WithTimeout(60 * time.Second),
)
  1. Check network connectivity
# Test API reachability
curl https://api.sent.dm/v3/health
  1. Implement circuit breaker
class CircuitBreaker {
  private failures = 0;
  private lastFailureTime?: number;
  private readonly threshold = 5;
  private readonly timeout = 60000;

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.isOpen()) {
      throw new Error('Circuit breaker is open');
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private isOpen(): boolean {
    if (this.failures < this.threshold) return false;
    if (!this.lastFailureTime) return false;
    return Date.now() - this.lastFailureTime < this.timeout;
  }

  private onSuccess() {
    this.failures = 0;
  }

  private onFailure() {
    this.failures++;
    this.lastFailureTime = Date.now();
  }
}

Debugging Tips

Enable Debug Logging

Most SDKs support debug logging via environment variables:

# Set environment variable
export SENT_DM_LOG=debug

Or configure in code:

const client = new SentDm({
  logLevel: 'debug'  // 'debug', 'info', 'warn', 'error', 'off'
});
# Set environment variable
export SENT_DM_LOG=debug

Or use Python logging:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('sent_dm')
logger.setLevel(logging.DEBUG)
// Use the debug option
client := sentdm.NewClient(
    option.WithDebugLog(nil),
)
# Set system property
java -Dsentdm.log.level=DEBUG -jar myapp.jar

Log Request IDs

Always log information for support tickets:

try {
  const response = await client.messages.send({
    to: ['+1234567890'],
    template: {
      id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
      name: 'welcome'
    }
  });
  console.log('Message sent:', response.data.messages[0].id);
} catch (error) {
  if (error instanceof SentDm.APIError) {
    console.log('Request ID:', error.headers['x-request-id']);
    console.log('Status:', error.status);
    console.log('Message:', error.message);
    // Include these in support tickets!
  }
}
from sent_dm import APIStatusError

try:
    response = client.messages.send(
        to=['+1234567890'],
        template={
            'id': '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
            'name': 'welcome'
        }
    )
    print(f'Message sent: {response.data.messages[0].id}')
except APIStatusError as e:
    print(f'Request ID: {e.response.headers.get("x-request-id")}')
    print(f'Status: {e.status_code}')
    print(f'Message: {e.message}')
    # Include these in support tickets!
response, err := client.Messages.Send(ctx, params)
if err != nil {
    var apiErr *sentdm.Error
    if errors.As(err, &apiErr) {
        fmt.Printf("Request ID: %s\n", apiErr.RequestID)
        fmt.Printf("Status: %d\n", apiErr.StatusCode)
        fmt.Printf("Message: %s\n", apiErr.Message)
        // Include these in support tickets!
    }
} else {
    fmt.Printf("Message sent: %s\n", response.Data.Messages[0].ID)
}

Test with cURL

Compare SDK behavior with raw API calls:

# Test authentication
curl -X GET https://api.sent.dm/v3/templates \
  -H "x-api-key: YOUR_API_KEY"

# Test message sending
curl -X POST https://api.sent.dm/v3/messages \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+1234567890",
    "template_id": "your-template-id"
  }'

Frequently Asked Questions

Q: Why is my message stuck in "pending" status?

A: Messages start as "pending" and transition through:

  1. pending → Message accepted, queued for sending
  2. sent → Dispatched to carrier/WhatsApp
  3. delivered → Confirmed delivery
  4. read → (WhatsApp only) Opened by recipient

Use webhooks to track status changes. Polling is not supported.

Q: Can I use the same API key for multiple environments?

A: Yes, but it's not recommended. Use separate keys for different environments and explicitly pass them to the SDK:

// Choose key based on environment
const apiKey = process.env.NODE_ENV === 'production'
  ? process.env.SENT_DM_API_KEY
  : process.env.SENT_DM_API_KEY_TEST;

const client = new SentDm(apiKey);

This prevents accidental sends from test environments. Note: The SDK only automatically reads SENT_DM_API_KEY - you must implement the environment switching logic yourself.

Q: Why am I getting 401 errors in production but not locally?

A: Common causes:

  1. Different API keys (check environment variables)
  2. Key not set in production environment
  3. Key was revoked/rotated
  4. Whitespace or encoding issues

Debug by logging the key prefix:

console.log('Key prefix:', process.env.SENT_DM_API_KEY?.substring(0, 8));

Q: How do I handle webhook failures?

A: Sent will retry failed webhooks with exponential backoff:

  • Retry 1: After 1 minute
  • Retry 2: After 5 minutes
  • Retry 3: After 15 minutes

Ensure your endpoint is idempotent and responds quickly.

Q: Can I send messages from the browser?

A: No. Never expose your API key in client-side code. API keys should only be used server-side. For browser-based messaging, route through your backend API.


Getting Help

If you're still stuck:

  1. Check the API Reference for detailed endpoint documentation
  2. Review SDK Guides for language-specific examples
  3. Contact Support with your request ID:
    • Email: support@sent.dm
    • Include: Request ID from error response (in x-request-id header)
    • Include: Timestamp of the failed request
    • Include: Code snippet (remove API keys!)

When contacting support, always include the request ID from the API response headers (x-request-id). This helps us trace the exact request in our logs.


SDK-Specific Error Patterns

Different SDKs handle errors differently. Here's a quick reference:

SDKError PatternKey Exception Types
TypeScriptThrows exceptionsAPIError, BadRequestError, RateLimitError, AuthenticationError
PythonThrows exceptionsAPIError, BadRequestError, RateLimitError, AuthenticationError
GoReturns error value*sentdm.Error with StatusCode, Message, RequestID
JavaThrows exceptionsSentDmException, BadRequestException, RateLimitException
C#Throws exceptionsSentDmApiException, SentDmBadRequestException, SentDmRateLimitException
PHPThrows exceptionsAPIException, BadRequestException, RateLimitException
RubyThrows exceptionsAPIError, BadRequestError, RateLimitError

On this page