Bento PHP Guide
Pull in bentonow/bento-php-sdk, store your Site UUID plus API keys in ENV, and start tracking events or syncing subscribers with a single service container.
Install the Bento PHP SDK with Composer, authenticate requests, and run the same event, subscriber, and transactional email workflows that power every other SDK.
Getting Started
Beginner Guide
Intermediate Guide
Advanced Guide
Reference
Getting Started
Step 1
Install the Bento PHP SDK
Install the SDK with your framework’s package manager and keep it alongside your other HTTP clients so configuration, logging, and credentials stay centralized.
Using Composer
bashcomposer require bentonow/bento-php-sdkStep 2
Configure the client
Store your Site UUID, publishable key, and secret key in environment variables or a secrets manager. Register the Bento services during your app’s bootstrap so every controller, job, or command reuses the same client.
basic_setup
php<?php
use bentonow\Bento\BentoAnalytics;
// Initialize the Bento client
$bento = new BentoAnalytics([
'authentication' => [
'secretKey' => 'YOUR_SECRET_KEY',
'publishableKey' => 'YOUR_PUBLISHABLE_KEY'
],
'siteUuid' => 'YOUR_SITE_UUID'
]);Beginner Guide
Tracking your first event
Events are the fastest way to add subscribers, kick off automations, and capture context in a single API call. Track onboarding milestones, purchases, or page views, then personalize with Liquid.
- Pair every event with subscriber metadata so flows can branch without extra imports.
- Include transactional context (cart items, amounts, IDs) for downstream personalization.
- Prefer events over bespoke subscriber mutations—automations can add tags or update fields later.
| Detail | Liquid tag |
|---|---|
| Product details | {{ event.details.product.size }} |
| Purchase amount | {{ event.details.value.amount }} |
| Transaction ID | {{ event.details.unique.key }} |
| Payment method | {{ event.details.value.payment_method }} |
Track a simple page view
php$bento->V1->track([
'email' => 'user@example.com',
'type' => '$pageView',
'details' => [
'url' => '/home',
'title' => 'Home Page',
],
]);Track a form submission
php$bento->V1->track([
'email' => 'user@example.com',
'type' => '$formSubmitted',
'details' => [
'formName' => 'Newsletter Signup',
'source' => 'Homepage',
],
]);Managing subscribers
You can still create, tag, or unsubscribe subscribers directly when you need to correct data or build admin tooling. Keep these calls reserved for operational work—events remain the preferred entry point.
Create a new subscriber
php$bento->V1->Subscribers->createSubscriber([
'email' => 'new@example.com',
]);Tag a subscriber
php$bento->V1->tagSubscriber([
'email' => 'user@example.com',
'tagName' => 'Newsletter',
]);Unsubscribe A subscriber
php$bento->V1->removeSubscriber([
'email' => 'user@example.com',
]);Common use cases
- Track onboarding, purchase, and lifecycle events so automations fire with one API call.
- Tag subscribers in response to events rather than separate API loops.
- Store contextual data (cart contents, plan tier) for segmentation and personalization.
Tracking a user login "Event"
php$bento->V1->track([
'email' => 'user@example.com',
'type' => '$login',
'details' => [
'method' => 'password',
'device' => 'mobile',
],
]);Updating user information
php$bento->V1->updateFields([
'email' => 'user@example.com',
'fields' => [
'account' => 'active',
'device' => 'mobile',
],
]);Adding multiple tags to a subscriber
php$bento->V1->Batch->importSubscribers([
'subscribers' => [
[
'email' => 'user@example.com',
'tags' => ['Premium', 'Annual Plan', 'Early Adopter'],
],
]
]);Intermediate Guide
Custom fields and tags
Combine fields for rich profile data with namespaced tags for segmentation. Tags keep audiences organized (`plan:pro`, `status:vip`) while fields store free-form values like locale or favorite product.
Namespaced tag ideas
Apply via automations whenever possible so marketing and data teams stay in sync.
Create a new custom field definition
php$field = $bento->V1->Fields->createField([
'key' => 'membershipLevel',
]);Get all existing fields
php$fields = $bento->V1->Fields->getFields();
// Now you can iterate through the fields
foreach ($fields as $field) {
echo "Field: " . $field['attributes']['name'] . " (Key: " . $field['attributes']['key'] . ")\n";
}Create a new tag
php$tag = $bento->V1->Tags->createTag([
'name' => 'Power User',
]);Get all existing tags
php$tags = $bento->V1->Tags->getTags();
// Now you can iterate through the tags
foreach ($tags as $tag) {
echo "Tag: " . $tag['attributes']['name'] . " (ID: " . $tag['id'] . ", Created: " . $tag['attributes']['created_at'] . ")\n";
}Tracking purchase events
Always include a `unique` key (order ID, cart token, etc.) so Bento dedupes purchases. Capture cart contents and totals to power LTV reporting and Liquid personalization.
Track a purchase event to monitor customer lifetime value
php$bento->V1->trackPurchase([
'email' => 'customer@example.com',
'purchaseDetails' => [
'unique' => [
'key' => 'order-123', // Unique order identifier
],
'value' => [
'amount' => 9999, // Amount in cents
'currency' => 'USD',
],
'cart' => [
'items' => [
[
'product_id' => 'prod-456',
'product_name' => 'Premium Widget',
'product_price' => 9999,
'quantity' => 1,
'product_sku' => 'SKU-456',
],
],
],
],
]);Namespaced tags
Reserve tags for segmentation keys (e.g., `product:analytics`). Use fields when you need arbitrary values or timestamps. This keeps your marketing taxonomy predictable.
Advanced Guide
Batch operations
Batch endpoints cover more than 80% of API work. Keep payloads lean, batch 200–300 records, and retry only failed chunks.
Import multiple subscribers at once
php$bento->V1->Batch->importSubscribers([
'subscribers' => [
[
'email' => 'user1@example.com',
'firstName' => 'Alice',
'company' => 'Acme Inc',
],
[
'email' => 'user2@example.com',
'firstName' => 'Bob',
'company' => 'Beta Corp',
],
// Can add up to 1,000 subscribers in a single batch
]
]);Import multiple events at once
phpuse bentonow\Bento\SDK\Batch\BentoEvents;
$bento->V1->Batch->importEvents([
'events' => [
[
'email' => 'user@example.com',
'type' => BentoEvents::SUBSCRIBE,
'date' => '2023-01-01',
],
[
'email' => 'user@example.com',
'type' => BentoEvents::PURCHASE,
'details' => [
'unique' => [
'key' => 'order-123',
],
'value' => [
'currency' => 'USD',
'amount' => 9999,
],
],
],
// Can add up to 1,000 events in a single batch
]
]);Transactional emails
Great candidates
- Onboarding confirmation or welcome sequences
- Password reset and login verification links
- Payment, receipt, or fulfillment notices
- Account or compliance notifications
Avoid using transactional for
- CC / BCC workflows or multi-recipient fan-out
- Attachments (link to files instead)
- Marketing, promotional, or newsletter content
- Bulk announcements where unsubscribes must be honored
Send a transactional email
php$results = $bento->V1->Emails->createEmail([
[
'to' => 'recipient@example.com',
'from' => 'sender@example.com',
'subject' => 'Reset Password',
'html_body' => '<p>Here is a link to reset your password ... {{ link }}</p>',
'transactional' => true,
'personalizations' => [
'link' => 'https://example.com/reset-password',
],
]
]);
echo "Successfully queued emails for delivery\n";Subscriber updates
Events remain the preferred way to create and update subscribers—they trigger flows and can mutate tags or fields downstream. Use direct endpoints when workflows demand determinism.
Events-first creation
Create a subscriber when they sign up
php$bento->V1->addSubscriber([
'email' => 'new-user@example.com',
'fields' => [
'firstName' => 'Jane',
'lastName' => 'Doe',
'signupSource' => 'website',
],
]);Create a subscriber when they make a purchase
php$bento->V1->trackPurchase([
'email' => 'customer@example.com',
'purchaseDetails' => [
'unique' => [
'key' => 'order-123',
],
'value' => [
'amount' => 9999,
'currency' => 'USD',
],
],
]);Direct creation options
Create a single subscriber (email only)
php$subscriber = $bento->V1->Subscribers->createSubscriber([
'email' => 'user@example.com',
]);Import multiple subscribers
php$bento->V1->Batch->importSubscribers([
'subscribers' => [
[
'email' => 'user1@example.com',
'membershipTier' => 'gold',
'accountStatus' => 'active',
'lastRenewalDate' => date('c'),
],
[
'email' => 'user2@example.com',
'membershipTier' => 'silver',
'accountStatus' => 'pending',
'trialEndsAt' => date('c', strtotime('+30 days')),
],
]
]);Event-driven profile updates
Update subscriber when they update their profile
php$bento->V1->updateFields([
'email' => 'user@example.com',
'fields' => [
'subscriptionTier' => 'premium',
],
]);Single attribute tweaks
Add or update a single field
php$bento->V1->Commands->addField([
'email' => 'user@example.com',
'field' => [
'key' => 'membershipTier',
'value' => 'premium',
],
]);Add a tag
php$bento->V1->Commands->addTag([
'email' => 'user@example.com',
'tagName' => 'example:tag',
]);Remove a field
php$bento->V1->Commands->removeField([
'email' => 'user@example.com',
'fieldName' => 'temporaryStatus',
]);Batch updates
Update multiple subscribers
php$bento->V1->Batch->importSubscribers([
'subscribers' => [
[
'email' => 'user1@example.com',
'membershipTier' => 'gold',
'accountStatus' => 'active',
'lastRenewalDate' => date('c'),
],
[
'email' => 'user2@example.com',
'membershipTier' => 'silver',
'accountStatus' => 'pending',
'trialEndsAt' => date('c', strtotime('+30 days')),
],
]
]);Specialized operations
Change a subscribers email address
php$bento->V1->Commands->addField([
'email' => 'old@example.com',
'field' => [
'key' => 'email',
'value' => 'new@example.com',
],
]);Update all fields at once
php$bento->V1->updateFields([
'email' => 'user@example.com',
'fields' => [
'firstName' => 'Updated',
'lastName' => 'Name',
'address' => [
'street' => '123 Main St',
'city' => 'New York',
'state' => 'NY',
'zip' => '10001',
],
'preferences' => [
'theme' => 'dark',
'notifications' => true,
],
],
]);Unsubscribe a user
php$bento->V1->Commands->unsubscribe([
'email' => 'user@example.com',
]);Utility features
Validate addresses, guess gender, geolocate IPs, or check blacklists without integrating third-party APIs. These helpers are rate-limited—cache responses when you can.
Validate Email
php$validationResult = $bento->V1->Experimental->validateEmail([
'email' => 'user@example.com',
'name' => 'John Doe', // Optional
'userAgent' => 'Mozilla/5.0...', // Optional
'ip' => '192.168.1.1', // Optional
]);
echo "Email is valid: " . ($validationResult ? 'true' : 'false') . "\n";Guess Prediction (for personalization)
php$genderResult = $bento->V1->Experimental->guessGender([
'name' => 'Alex',
]);
echo "Gender prediction: " . json_encode($genderResult) . "\n";Geolocate IP Address
php$locationResult = $bento->V1->Experimental->geolocate([
'ip' => '208.67.222.222',
]);
echo "Location: " . json_encode($locationResult) . "\n";Check Domain/IP blacklist
php$blacklistResult = $bento->V1->Experimental->checkBlacklist([
'domain' => 'example.com',
// Or check an IP instead: 'ip' => '192.168.1.1'
]);
echo "Blacklist status: " . json_encode($blacklistResult) . "\n";API Reference
The PHP SDK mirrors Bento's REST resources—most services expose async methods for batch operations plus convenience helpers for validation.
Core operations
-
Add a subscriber
$bento->V1->addSubscriber($payload)
Create subscriber -
Unsubscribe a subscriber
$bento->V1->removeSubscriber($payload)
Subscriber command -
Track a custom event
$bento->V1->track($event)
Events API -
Track a purchase
$bento->V1->trackPurchase($event)
Purchase events
Batch & commands
-
Import subscribers
$bento->V1->Batch->importSubscribers($payload)
Import subscribers -
Import events
$bento->V1->Batch->importEvents($payload)
Import events -
Send transactional email
$bento->V1->Emails->createEmail($payload)
Emails API -
Add a tag via commands
$bento->V1->Commands->addTag($payload)
Subscriber command
Utility helpers
-
Validate an email address
$bento->V1->Experimental->validateEmail($payload)
Validate email -
Guess gender
$bento->V1->Experimental->guessGender($payload)
Guess gender -
Check blacklist status
$bento->V1->Experimental->checkBlacklist($payload)
Blacklist search
Troubleshooting
Not Authorized
Verify publishable + secret keys and ensure the team member still has access.
Rate Limited
Implement exponential backoff for batch operations and honor Retry-After headers.
Network Errors
Confirm outbound traffic can reach app.bentonow.com and retry transient socket resets.
Payload Exceptions
Double-check Author emails and ensure payloads match the documented schema.
Debugging tips
- Enable `logErrors: true` and log the returned request IDs so payload issues are traceable in production.
- Wrap every SDK call in try/catch (or a queue job) and surface the original Guzzle exception details for faster incident resolution.
- Keep batch imports around 200–300 records until payloads validate, then scale toward 1,000 if you need more throughput.
FAQ
Can I use this SDK in a frontend environment?
No. The PHP SDK expects your publishable key, secret key, and Site UUID—secrets that must remain on the server. Use the JavaScript SDK or proxy requests for browser work.
How do I handle rate limiting?
Catch 429 responses, log the Retry-After header, and retry with exponential backoff so jobs throttle automatically instead of failing noisily.
rate_limiting
php// Simple retry with exponential backoff
function retryWithBackoff($callable, $maxRetries = 3) {
$attempt = 0;
$delay = 1; // Start with 1 second delay
while ($attempt < $maxRetries) {
try {
return $callable();
} catch (Exception $e) {
$attempt++;
if ($attempt >= $maxRetries) {
throw $e;
}
sleep($delay);
$delay *= 2; // Exponential backoff
}
}
}
// Usage
retryWithBackoff(function() use ($bento) {
return $bento->V1->track([
'email' => 'user@example.com',
'type' => '$custom.event',
]);
});What's the maximum batch size for importing subscribers or events?
The API accepts up to 1,000 subscribers or events in a single payload. In practice, batches of 200–300 are easier to debug and retry.
max_batch_size
php// Split subscribers into batches of 200
$allSubscribers = [/* your subscribers array */];
$batches = array_chunk($allSubscribers, 200);
// Process batches sequentially to avoid rate limits
foreach ($batches as $batch) {
try {
$bento->V1->Batch->importSubscribers([
'subscribers' => $batch
]);
// Add small delay between batches
sleep(1);
} catch (Exception $e) {
error_log("Batch import error: " . $e->getMessage());
}
}How do I track anonymous users?
Every event and subscriber mutation currently requires an email address. Capture emails early (forms, gated content, checkout) before calling the SDK.
Which PHP versions are supported?
Composer plus PHP 7.4 or higher is required, but we recommend PHP 8.1+ for performance and security improvements.
How can I contribute to the SDK?
Open pull requests or issues on github.com/bentonow/bento-php-sdk, or jump into the community Discord to share repros and feature ideas.
How do I handle errors and exceptions?
Wrap SDK calls in try/catch, inspect the exception payload, and notify your logging stack so you can correlate failures with request IDs.
context_support
php// Use try-catch for error handling
try {
$subscriber = $bento->V1->Subscribers->getSubscribers([
'email' => 'example@test.com'
]);
} catch (Exception $e) {
// Handle errors appropriately
error_log("API call failed: " . $e->getMessage());
}Can I use the SDK with serverless PHP environments?
Yes. Initialize the client once per container (Bref, Vapor, etc.), reuse it across invocations, and keep payloads lean to avoid cold start penalties.
serverless_support
php// Use static variables for connection reuse in serverless
class BentoService {
private static $client = null;
public static function getClient() {
if (self::$client === null) {
self::$client = new BentoAnalytics([
'authentication' => [
'secretKey' => $_ENV['BENTO_SECRET_KEY'],
'publishableKey' => $_ENV['BENTO_PUBLISHABLE_KEY']
],
'siteUuid' => $_ENV['BENTO_SITE_UUID']
]);
}
return self::$client;
}
}
// Usage in Lambda/serverless function
function handleRequest($event) {
$bento = BentoService::getClient();
// Use bento for operations
}Contribute or debug further
The PHP SDK is open source. Report bugs, request features, or open pull requests at https://github.com/bentonow/bento-php-sdk. Keep your local tooling on PHP 7.4+ (PHP 8.2 recommended) to match production builds.
Need the original Markdown? Open raw file