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

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

bash
composer require bentonow/bento-php-sdk

Step 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

subscription:basicsubscription:prosubscription:enterprise

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

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

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

  1. Enable `logErrors: true` and log the returned request IDs so payload issues are traceable in production.
  2. Wrap every SDK call in try/catch (or a queue job) and surface the original Guzzle exception details for faster incident resolution.
  3. 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