A CRM that fits naturally into Laravel's architecture
Laravel has conventions for everything — controllers, Eloquent observers, event listeners, queued jobs. TinyCRM's PHP SDK plugs into whichever pattern you prefer. Drop it in a controller, fire it from an Eloquent observer, or dispatch it as a background job. No new abstractions required.
Installation
composer require tinycrm/laravelThen publish the config:
php artisan vendor:publish --tag=tinycrm-configAdd to .env:
TINYCRM_API_KEY=tcrm_proj_xxxxxxxxxxxxWhere to place TinyCRM calls in Laravel
You have three good options. Choose based on how much separation of concerns you want.
Call identify() directly in your registration controller method. Simple and explicit.
Register a UserObserver. The created() method fires on every new user, keeping tracking out of business logic.
Dispatch a SyncToTinyCRM job for zero latency impact on your sign-up route.
Getting started: Laravel integration
Option A — Direct controller call
<?php
// app/Http/Controllers/Auth/RegisteredUserController.php
use TinyCRM\Laravel\Facades\TinyCRM;
class RegisteredUserController extends Controller
{
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
// Sync to TinyCRM — non-blocking
TinyCRM::identify([
'email' => $user->email,
'name' => $user->name,
'status' => 'free',
'params' => ['source' => $request->input('utm_source', 'direct')],
]);
Auth::login($user);
return redirect(RouteServiceProvider::HOME);
}
}Option B — Eloquent Observer (recommended)
<?php
// app/Observers/UserObserver.php
use TinyCRM\Laravel\Facades\TinyCRM;
class UserObserver
{
public function created(User $user): void
{
TinyCRM::identify([
'email' => $user->email,
'name' => $user->name,
'status' => 'free',
]);
}
public function updated(User $user): void
{
if ($user->wasChanged('subscription_status')) {
TinyCRM::identify([
'email' => $user->email,
'status' => $user->subscription_status, // 'free' or 'paid'
'params' => ['plan' => $user->plan],
]);
}
}
}
// app/Providers/AppServiceProvider.php
User::observe(UserObserver::class);Option C — Queued Job for zero latency impact
<?php
// app/Jobs/SyncCustomerToTinyCRM.php
use TinyCRM\Laravel\Facades\TinyCRM;
class SyncCustomerToTinyCRM implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
private readonly string $email,
private readonly string $name,
private readonly string $status,
private readonly array $params = []
) {}
public function handle(): void
{
TinyCRM::identify([
'email' => $this->email,
'name' => $this->name,
'status' => $this->status,
'params' => $this->params,
]);
}
}
// Dispatch from anywhere
dispatch(new SyncCustomerToTinyCRM($user->email, $user->name, 'free'));Handle subscription webhooks (Stripe / Paddle)
<?php
// app/Http/Controllers/WebhookController.php
use TinyCRM\Laravel\Facades\TinyCRM;
class WebhookController extends Controller
{
public function handleStripe(Request $request): Response
{
$event = $this->verifyStripeSignature($request);
if ($event->type === 'checkout.session.completed') {
$email = $event->data->object->customer_email;
TinyCRM::identify([
'email' => $email,
'status' => 'paid',
'params' => ['plan' => $event->data->object->metadata->plan],
]);
}
return response('', 200);
}
}Laravel ecosystem
Eloquent ORM
Observer pattern keeps tracking decoupled from business logic.
Laravel Queues
Dispatch as background job for high-throughput routes.
Laravel Sanctum
Works with Sanctum token auth — call identify() in login handlers.
Laravel Breeze/Jetstream
Add observer on User model created by Breeze scaffolding.
Cashier (Stripe)
Use chargebacked/subscription events to update status.
Forge / Vapor
Works on both VM deployments and Laravel Vapor serverless.
Laravel FAQ
Does the TinyCRM PHP SDK work with Laravel Octane?
Yes. The SDK is stateless — it creates no shared state between requests. Each call instantiates a fresh HTTP request to the TinyCRM API, making it fully compatible with Laravel Octane's long-running process model.
Should I call identify() synchronously or via a queued job?
For most apps, a synchronous fire-and-forget call is fine — the SDK call is a single lightweight HTTP request taking under 100ms. For high-traffic apps or strict SLA routes, wrap it in a queued job (dispatch(new SyncCustomerToTinyCRM($user))) to guarantee zero latency impact.
Can I use Eloquent observers instead of calling identify() in controllers?
Yes — that's the recommended pattern for Laravel. Create a UserObserver and call TinyCRM in the created() and updated() methods. Register it in AppServiceProvider. This keeps tracking logic out of your controllers.
How do I handle multiple Laravel apps sending to the same TinyCRM account?
Create a separate TinyCRM project for each Laravel app, each with its own API key (set per app in .env). Customers are merged by email in your dashboard — the same user across two apps becomes one row with two project badges.
Ready to see all your Laravel customers?
14-day free trial. No credit card. Works with any Laravel 10+ app.
composer require tinycrm/laravel