PHP SDK

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-php

To 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 Dashboard

Send 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);       // bool

Handling 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:

CauseError Type
HTTP 400BadRequestException
HTTP 401AuthenticationException
HTTP 403PermissionDeniedException
HTTP 404NotFoundException
HTTP 409ConflictException
HTTP 422UnprocessableEntityException
HTTP 429RateLimitException
HTTP >= 500InternalServerException
Other HTTP errorAPIStatusException
TimeoutAPITimeoutException
Network errorAPIConnectionException

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). Use message.received to 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:

Source & Issues

Getting Help


On this page