PHP SDK
The official PHP SDK for Sent provides a clean, object-oriented interface for sending messages. Built with modern PHP 8.1+ features.
Requirements
PHP 8.1.0 or higher.
Installation
composer require sentdm/sent-dm-phpTo install a specific version:
composer require "sentdm/sent-dm-php 0.26.0"Quick Start
Initialize the client
<?php
require_once 'vendor/autoload.php';
use SentDm\Client;
$client = new Client($_ENV['SENT_DM_API_KEY']); // Your API key from the Sent DashboardSend your first message
<?php
require_once 'vendor/autoload.php';
use SentDm\Client;
$client = new Client($_ENV['SENT_DM_API_KEY']);
$result = $client->messages->send(
to: ['+1234567890'],
template: [
'id' => '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
'name' => 'welcome',
'parameters' => [
'name' => 'John Doe',
'order_id' => '12345'
]
],
channel: ['sms', 'whatsapp', 'rcs'] // Optional
);
var_dump($result->data->recipients[0]->messageID);
var_dump($result->data->status);Authentication
The client accepts an API key as the first parameter.
use SentDm\Client;
// Using API key directly
$client = new Client('your_api_key');
// Or from environment variable
$client = new Client($_ENV['SENT_DM_API_KEY']);Send Messages
This library uses named parameters to specify optional arguments. Parameters with a default value must be set by name.
Send a message
$result = $client->messages->send(
to: ['+1234567890'],
template: [
'id' => '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
'name' => 'welcome',
'parameters' => [
'name' => 'John Doe',
'order_id' => '12345'
]
],
channel: ['sms', 'whatsapp', 'rcs']
);
var_dump($result->data->recipients[0]->messageID);
var_dump($result->data->status);Sandbox mode
Use sandbox: true to validate requests without sending real messages:
$result = $client->messages->send(
to: ['+1234567890'],
template: [
'id' => '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
'name' => 'welcome'
],
sandbox: true // Validates but doesn't send
);
// Response will have test data
var_dump($result->data->recipients[0]->messageID);
var_dump($result->data->status);Check message status
Retrieve the current status of a sent message. The direction field indicates whether the message is "OUTBOUND" (sent by you) or "INBOUND" (a reply or opt-out keyword received from an end user):
$status = $client->messages->retrieveStatus('msg-uuid');
var_dump($status->data->status); // e.g. "DELIVERED"
var_dump($status->data->channel); // e.g. "sms"
var_dump($status->data->direction); // "OUTBOUND" | "INBOUND"Message activities
Retrieve the full activity log for a message, useful for auditing delivery attempts across carriers:
$activities = $client->messages->retrieveActivities('msg-uuid');
foreach ($activities->data->activities as $activity) {
echo $activity->timestamp . ': ' . $activity->status . ' via ' . $activity->from . "\n";
echo ' Price: ' . $activity->price . "\n";
echo ' Active contact price: ' . $activity->activeContactPrice . "\n";
}Numbers
Look up carrier and line-type information for any phone number before sending:
$result = $client->numbers->lookup('+12025551234');
var_dump($result->data->isValid); // bool
var_dump($result->data->carrierName); // e.g. "T-Mobile"
var_dump($result->data->lineType); // "mobile", "landline", "voip"
var_dump($result->data->isVoip); // boolHandling errors
When the library is unable to connect to the API, or if the API returns a non-success status code (that is, 4xx or 5xx response), a subclass of SentDm\Core\Exceptions\APIException will be thrown:
use SentDm\Core\Exceptions\APIConnectionException;
use SentDm\Core\Exceptions\RateLimitException;
use SentDm\Core\Exceptions\APIStatusException;
try {
$result = $client->messages->send(
to: ['+1234567890'],
template: [
'id' => '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
'name' => 'welcome'
]
);
} catch (APIConnectionException $e) {
echo "The server could not be reached", PHP_EOL;
var_dump($e->getPrevious());
} catch (RateLimitException $e) {
echo "A 429 status code was received; we should back off a bit.", PHP_EOL;
} catch (APIStatusException $e) {
echo "Another non-200-range status code was received", PHP_EOL;
echo $e->getMessage();
}Error codes are as follows:
| Cause | Error Type |
|---|---|
| HTTP 400 | BadRequestException |
| HTTP 401 | AuthenticationException |
| HTTP 403 | PermissionDeniedException |
| HTTP 404 | NotFoundException |
| HTTP 409 | ConflictException |
| HTTP 422 | UnprocessableEntityException |
| HTTP 429 | RateLimitException |
| HTTP >= 500 | InternalServerException |
| Other HTTP error | APIStatusException |
| Timeout | APITimeoutException |
| Network error | APIConnectionException |
Retries
Certain errors will be automatically retried 2 times by default, with a short exponential backoff.
// Configure the default for all requests:
$client = new Client($_ENV['SENT_DM_API_KEY'], requestOptions: ['maxRetries' => 0]);
// Or, configure per-request:
$result = $client->messages->send(
to: ['+1234567890'],
template: [
'id' => '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
'name' => 'welcome'
],
requestOptions: ['maxRetries' => 5],
);Value Objects
Sent recommends the static with constructor and named parameters to initialize value objects.
use SentDm\Models\TemplateDefinition;
$definition = TemplateDefinition::with(
body: [...],
header: [...],
);However, builders are also provided:
$definition = (new TemplateDefinition)->withBody([...]);Contacts
Create and manage contacts:
// Create a contact
$result = $client->contacts->create(phoneNumber: '+1234567890');
var_dump($result->data->id);
// List contacts
$result = $client->contacts->list(page: 1, pageSize: 100);
foreach ($result->data->contacts as $contact) {
echo $contact->phoneNumber . " - " . $contact->availableChannels . "\n";
}
// Get a contact
$result = $client->contacts->retrieve('contact-uuid');
// Update a contact
$result = $client->contacts->update('contact-uuid', defaultChannel: 'whatsapp');
// Delete a contact
$client->contacts->delete('contact-uuid');Templates
List and retrieve templates:
// List templates
$result = $client->templates->list(page: 1, pageSize: 100);
foreach ($result->data->templates as $template) {
echo $template->name . " (" . $template->status . "): " . $template->id . "\n";
echo " Category: " . $template->category . "\n";
}
// Get a specific template
$result = $client->templates->retrieve('template-uuid');
echo "Name: " . $result->data->name . "\n";
echo "Status: " . $result->data->status . "\n";Webhooks
Recommended pattern: Webhooks are the primary way to track message delivery, so don't poll the API. Save the message ID when you send, then update your database as webhook events arrive.
Sent delivers signed POST requests to your endpoint for every status change. Two event types exist:
message: Message status changes (QUEUED,ROUTED,SCHEDULED,SENT,DELIVERED,READ,FAILED,FILTERED,BLOCKED,RECEIVED); each fires as a sub-type (for example,message.delivered,message.filtered). Usemessage.receivedto receive inbound messages from contacts.templates: WhatsApp template approval/rejection
The signing secret (from the Sent Dashboard) has a whsec_ prefix. Strip it and base64-decode the remainder to obtain the raw HMAC key. The signed content is {X-Webhook-ID}.{X-Webhook-Timestamp}.{rawBody} and the signature format is v1,{base64(hmac)}.
The following example uses plain PHP (php://input and $_SERVER) and works in any PHP app:
<?php
// webhook.php — plain PHP endpoint for Sent webhooks
// 1. Read the raw body and signature headers
$payload = file_get_contents('php://input');
$webhookId = $_SERVER['HTTP_X_WEBHOOK_ID'] ?? '';
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
// 2. Verify: signed content = "{webhookId}.{timestamp}.{rawBody}"
$secret = getenv('SENT_DM_WEBHOOK_SECRET'); // "whsec_abc123..."
$keyBase64 = str_starts_with($secret, 'whsec_') ? substr($secret, 6) : $secret;
$keyBytes = base64_decode($keyBase64);
$signed = "{$webhookId}.{$timestamp}.{$payload}";
$expected = 'v1,' . base64_encode(hash_hmac('sha256', $signed, $keyBytes, true));
header('Content-Type: application/json');
if (!hash_equals($expected, $signature)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
// 3. Optional: reject replayed events older than 5 minutes
if (abs(time() - intval($timestamp)) > 300) {
http_response_code(401);
echo json_encode(['error' => 'Timestamp too old']);
exit;
}
$event = json_decode($payload);
// 4. Handle events — update message status in your own database
if ($event->field === 'message') {
if ($event->event === 'message.received') {
// Inbound message from a contact:
// $event->payload->inbound_number, $event->payload->text, $event->payload->channel
} else {
// Outbound status update:
// $event->payload->message_id, $event->payload->message_status
}
}
// 5. Always return 200 quickly
http_response_code(200);
echo json_encode(['received' => true]);Framework-specific handlers are available in the Laravel integration guide and the Symfony integration guide. See the Webhooks reference for the full payload schema and all status values.
Making custom or undocumented requests
Undocumented properties
You can send undocumented parameters to any endpoint using the extra* parameters:
$result = $client->messages->send(
to: ['+1234567890'],
template: [
'id' => '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
'name' => 'welcome'
],
requestOptions: [
'extraQueryParams' => ['my_query_parameter' => 'value'],
'extraBodyParams' => ['my_body_parameter' => 'value'],
'extraHeaders' => ['my-header' => 'value'],
],
);Undocumented endpoints
To make requests to undocumented endpoints while retaining the benefit of auth, retries, and so on:
$response = $client->request(
method: 'post',
path: '/undocumented/endpoint',
query: ['dog' => 'woof'],
headers: ['useful-header' => 'interesting-value'],
body: ['hello' => 'world']
);Framework Integration
Dedicated guides cover client setup, message sending, verified webhook handling, and testing for each framework:
Laravel
Bind the client in a service provider, send messages from a controller, verify webhooks, and test with sandbox mode.
Symfony
Register the client service, send messages from a controller, verify webhooks, and test with sandbox mode.
Source & Issues
- Version: 0.26.0
- GitHub:
sentdm/sent-dm-php - Packagist:
sentdm/sent-dm-php - Issues: Report a bug
Getting Help
- Documentation: API Reference
- Troubleshooting: Common Issues
- Support: email support@sent.dm with your request ID
Sending messages from ASP.NET Core with the Sent C# SDK
Wire the Sent C# SDK into an ASP.NET Core app: install, register the client with DI, send from an endpoint, verify webhooks, and test with sandbox mode.
Sending messages from Laravel with the Sent PHP SDK
Wire the Sent PHP SDK into a Laravel app: install, bind the client in a provider, send messages from a controller, verify webhooks, and test with sandbox mode.