userscape/customerio
PHP client for the Customer.io API. Create, update, and delete customers, fire events (including historical/anonymous), and record pageviews. Returns a Response object with success() and message() for simple error handling.
Install the Package:
composer require userscape/customerio
Configure Credentials:
Add to .env:
CUSTOMERIO_SITE_ID=your_site_id
CUSTOMERIO_API_SECRET=your_api_secret
Bind to Laravel Container:
In AppServiceProvider or a dedicated service provider:
use Customerio\Api;
use Customerio\Request;
public function register()
{
$this->app->singleton(Api::class, function ($app) {
return new Api(
config('services.customerio.site_id'),
config('services.customerio.api_secret'),
new Request()
);
});
}
First Use Case: Fire a user signup event in a controller:
use Customerio\Api;
public function handleSignup(Request $request, Api $customerio)
{
$response = $customerio->fireEvent(
$request->user()->id,
'user_signed_up',
['plan' => $request->input('plan')]
);
if (!$response->success()) {
Log::error("Customer.io event failed: " . $response->message());
}
}
fireEvent(), createCustomer(), and recordPageview() for 80% of use cases.$response->success() before proceeding.$customerio->createCustomer(
$user->id, // Customer ID
$user->email, // Email
['name' => $user->name] // Custom attributes
);
$customerio->updateCustomer(
$user->id,
['last_login' => now()->toDateTimeString()]
);
$customerio->deleteCustomer($user->id);
$customerio->fireEvent(
$user->id,
'purchase_completed',
['amount' => $order->total, 'items' => $order->items]
);
$customerio->fireEvent(
$user->id,
'old_event',
['data' => 'value'],
strtotime('2023-01-01') // Unix timestamp
);
$customerio->fireAnonymousEvent(
'page_viewed',
['page_url' => $request->url()]
);
$customerio->recordPageview(
$user->id,
'https://example.com/checkout',
'https://example.com/cart'
);
// app/Http/Middleware/TrackPageviews.php
public function handle(Request $request, Closure $next, Api $customerio)
{
$response = $next($request);
if ($user = auth()->user()) {
$customerio->recordPageview(
$user->id,
$request->url(),
$request->header('Referer')
);
}
return $response;
}
Service Binding: Use a facade for cleaner syntax:
// config/app.php
'aliases' => [
'Customerio' => Customerio\Facades\Customerio::class,
];
// app/Facades/Customerio.php
namespace Customerio\Facades;
use Illuminate\Support\Facades\Facade;
class Customerio extends Facade { protected static function getFacadeAccessor() { return 'customerio'; } }
Now use Customerio::fireEvent(...) anywhere.
Queued Events: Offload event firing to a queue to avoid blocking requests:
// app/Jobs/FireCustomerioEvent.php
public function handle()
{
$customerio = app(Api::class);
$customerio->fireEvent($this->userId, $this->event, $this->data);
}
Dispatch in your controller:
FireCustomerioEvent::dispatch($user->id, 'event_name', $data)->onQueue('customerio');
Model Observers: Automate event firing for Eloquent models:
// app/Observers/UserObserver.php
public function created(User $user)
{
$customerio = app(Api::class);
$customerio->fireEvent($user->id, 'user_created', $user->toArray());
}
Sync on Model Save:
// app/Models/User.php
protected static function booted()
{
static::saved(function ($user) {
if ($user->wasRecentlyCreated) {
$customerio = app(Api::class);
$customerio->createCustomer($user->id, $user->email, $user->attributesToArray());
} else {
$customerio->updateCustomer($user->id, $user->getDirty());
}
});
}
Batch Updates: For large datasets, use Laravel’s chunking:
User::chunk(100, function ($users) {
$customerio = app(Api::class);
foreach ($users as $user) {
$customerio->updateCustomer($user->id, ['last_active' => now()]);
}
});
Global Exception Handler: Catch Customer.io API failures:
// app/Exceptions/Handler.php
public function report(Throwable $exception)
{
if ($exception instanceof \Customerio\Exception) {
Log::error("Customer.io API error: " . $exception->getMessage());
}
parent::report($exception);
}
Retry Logic:
Use Laravel’s retry helper for transient failures:
retry(5, function () use ($customerio, $user, $event) {
$response = $customerio->fireEvent($user->id, $event, $data);
return $response->success() ?: false;
}, 100); // Retry after 100ms
API Key Exposure:
siteId/apiSecret in config files..env and validate keys on startup:
if (empty(config('services.customerio.site_id'))) {
throw new \RuntimeException("Customer.io site ID not configured.");
}
Deprecated Guzzle Version:
composer.json:
"config": {
"preferred-install": "dist",
"allow-plugins": {
"php-http/discovery": true
}
}
Or use a Guzzle 6 polyfill.No Async Support:
fireEvent).Response Parsing:
Response object’s message() may return raw JSON for errors.$response = $customerio->fireEvent(...);
if (!$response->success()) {
$error = json_decode($response->message(), true);
Log::error("Customer.io error: " . $error['message'] ?? $response->message());
}
Timestamp Handling:
$timestamp = Carbon::parse('2023-01-01')->timestamp();
$customerio->fireEvent($user->id, 'event', [], $timestamp);
Anonymous Event Limits:
session()->id) and resolve it post-signup.Data Size Limits:
updateCustomer with 100+ attributes) may fail.How can I help you explore Laravel packages today?