SDK Troubleshooting
Resolve common issues with Sent 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 keySolutions:
- Verify your API key from the Sent Dashboard
- Check for extra whitespace or copy-paste errors
- Ensure you're using the right environment variable (
SENT_DM_API_KEY) - 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 foundSolutions:
Contact opted out
Symptoms:
BadRequestError: 400 - Contact has opted out of messagingSolution: 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 foundSolutions:
400 Bad Request - WhatsApp template pending
Symptoms:
BadRequestError: 400 - Template is not approved for WhatsAppSolutions:
-
Check template status in dashboard
- WhatsApp templates need Meta approval (can take hours)
- SMS templates work immediately
-
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: 60Solutions:
Billing Errors
Payment Required - Account balance too low
Symptoms:
BadRequestError: 400 - Insufficient credits to send messageSolutions:
-
Add credits in the dashboard
- Go to Billing
-
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 30000msSolutions:
- Increase timeout
const client = new SentDm({
timeout: 60000 // 60 seconds
});from sent_dm import SentDm
client = SentDm(timeout=60.0) # 60 secondsclient := sentdm.NewClient(
option.WithTimeout(60 * time.Second),
)- Check network connectivity
# Test API reachability
curl https://api.sent.dm/v3/health- 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=debugOr configure in code:
const client = new SentDm({
logLevel: 'debug' // 'debug', 'info', 'warn', 'error', 'off'
});# Set environment variable
export SENT_DM_LOG=debugOr 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.jarLog 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:
pending→ Message accepted, queued for sendingsent→ Dispatched to carrier/WhatsAppdelivered→ Confirmed deliveryread→ (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:
- Different API keys (check environment variables)
- Key not set in production environment
- Key was revoked/rotated
- 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:
- Check the API Reference for detailed endpoint documentation
- Review SDK Guides for language-specific examples
- Contact Support with your request ID:
- Email: support@sent.dm
- Include: Request ID from error response (in
x-request-idheader) - 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:
| SDK | Error Pattern | Key Exception Types |
|---|---|---|
| TypeScript | Throws exceptions | APIError, BadRequestError, RateLimitError, AuthenticationError |
| Python | Throws exceptions | APIError, BadRequestError, RateLimitError, AuthenticationError |
| Go | Returns error value | *sentdm.Error with StatusCode, Message, RequestID |
| Java | Throws exceptions | SentDmException, BadRequestException, RateLimitException |
| C# | Throws exceptions | SentDmApiException, SentDmBadRequestException, SentDmRateLimitException |
| PHP | Throws exceptions | APIException, BadRequestException, RateLimitException |
| Ruby | Throws exceptions | APIError, BadRequestError, RateLimitError |