Skip to main content

Laravel Integration

Synchronize customer data from Laravel applications to AsaHome Cloud.

Overview

When customers are created in your Laravel application (e.g., after signing a service agreement), they can be automatically provisioned as users in AsaHome Cloud. This enables seamless onboarding without requiring users to create separate accounts.

Authentication

Laravel integration uses internal API keys for service-to-service authentication.

Generate API Key

Create a secure API key:

openssl rand -base64 32

Configure AsaHome Cloud

Add the key to your .env file:

INTERNAL_API_KEYS=your-laravel-api-key-here,other-service-key

Multiple keys can be comma-separated for different services.

Configure Laravel

Add the AsaHome Cloud configuration to your Laravel app:

// config/services.php
return [
// ... other services

'asacloud' => [
'base_url' => env('ASACLOUD_BASE_URL', 'https://cloud.asahome.io/api/v1'),
'api_key' => env('ASACLOUD_API_KEY'),
],
];
# .env
ASACLOUD_BASE_URL=https://cloud.asahome.io/api/v1
ASACLOUD_API_KEY=your-laravel-api-key-here

Sync Endpoint

Request

POST /api/v1/auth/sync-customer
X-Internal-Api-Key: your-laravel-api-key
Content-Type: application/json

Request Body

{
"id": 123,
"user_id": 456,
"company_name": "Acme Corp",
"first_name": "John",
"last_name": "Doe",
"email": "john@acme.com",
"user_type": 1,
"mrr": 99.99,
"active": true,
"contract_language": "en",
"created_by": 1,
"status": 1
}

Field Reference

FieldTypeRequiredDescription
idintegerYesCustomer ID in Laravel
user_idintegerNoAssociated user ID
company_namestringNoCompany/organization name
first_namestringYesCustomer first name
last_namestringYesCustomer last name
emailstringYesCustomer email (unique)
user_typeintegerNoUser type identifier
mrrdecimalNoMonthly recurring revenue
activebooleanYesAccount active status
contract_languagestringNoPreferred language (en, nl, etc.)
created_byintegerNoAdmin who created the customer
statusintegerNoCustomer status code

Response

Success

{
"success": true,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"sync_id": 123,
"email": "john@acme.com",
"firstName": "John",
"lastName": "Doe",
"isActive": true,
"createdAt": "2024-01-15T10:30:00.000Z"
},
"created": true
}

The created field indicates whether a new user was created (true) or an existing user was updated (false).

Error

{
"success": false,
"error": "Email already exists with different sync_id",
"code": "CONFLICT"
}

Laravel Implementation

Customer Sync Service

Create a service class to handle synchronization:

<?php

namespace App\Services;

use App\Models\Customer;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class CustomerSyncService
{
private string $baseUrl;
private string $apiKey;

public function __construct()
{
$this->baseUrl = config('services.asacloud.base_url');
$this->apiKey = config('services.asacloud.api_key');
}

/**
* Sync a customer to AsaHome Cloud
*/
public function syncCustomer(Customer $customer, User $user): array
{
try {
$response = Http::withHeaders([
'X-Internal-Api-Key' => $this->apiKey,
'Content-Type' => 'application/json',
])->post("{$this->baseUrl}/auth/sync-customer", [
'id' => $customer->id,
'user_id' => $customer->user_id,
'company_name' => $user->name,
'first_name' => $user->fname,
'last_name' => $user->lname,
'email' => $user->email,
'user_type' => $customer->user_type,
'mrr' => $customer->mrr,
'active' => $customer->active,
'contract_language' => $customer->contract_language ?? 'en',
'created_by' => $customer->created_by,
'status' => $customer->status,
]);

if ($response->successful()) {
$data = $response->json();

Log::info('Customer synced to AsaHome Cloud', [
'customer_id' => $customer->id,
'cloud_user_id' => $data['user']['id'],
'created' => $data['created'],
]);

return [
'success' => true,
'data' => $data,
];
}

Log::error('Failed to sync customer', [
'customer_id' => $customer->id,
'status' => $response->status(),
'error' => $response->json(),
]);

return [
'success' => false,
'error' => $response->json()['error'] ?? 'Unknown error',
];

} catch (\Exception $e) {
Log::error('Exception syncing customer', [
'customer_id' => $customer->id,
'exception' => $e->getMessage(),
]);

return [
'success' => false,
'error' => $e->getMessage(),
];
}
}

/**
* Sync multiple customers (batch)
*/
public function syncBatch(array $customerIds): array
{
$results = [];

foreach ($customerIds as $customerId) {
$customer = Customer::with('user')->find($customerId);

if ($customer && $customer->user) {
$results[$customerId] = $this->syncCustomer(
$customer,
$customer->user
);
} else {
$results[$customerId] = [
'success' => false,
'error' => 'Customer or user not found',
];
}
}

return $results;
}
}

Event-Based Sync

Automatically sync when customers are created:

<?php

namespace App\Listeners;

use App\Events\CustomerCreated;
use App\Services\CustomerSyncService;

class SyncCustomerToCloud
{
private CustomerSyncService $syncService;

public function __construct(CustomerSyncService $syncService)
{
$this->syncService = $syncService;
}

public function handle(CustomerCreated $event): void
{
$customer = $event->customer;
$user = $customer->user;

if ($user) {
$this->syncService->syncCustomer($customer, $user);
}
}
}

Register the listener:

// app/Providers/EventServiceProvider.php
protected $listen = [
CustomerCreated::class => [
SyncCustomerToCloud::class,
],
];

Artisan Command

Create a command for manual/batch sync:

<?php

namespace App\Console\Commands;

use App\Models\Customer;
use App\Services\CustomerSyncService;
use Illuminate\Console\Command;

class SyncCustomersToCloud extends Command
{
protected $signature = 'customers:sync-cloud
{--all : Sync all customers}
{--id=* : Specific customer IDs}';

protected $description = 'Sync customers to AsaHome Cloud';

public function handle(CustomerSyncService $syncService): int
{
if ($this->option('all')) {
$customers = Customer::with('user')
->where('active', true)
->get();
} elseif ($ids = $this->option('id')) {
$customers = Customer::with('user')
->whereIn('id', $ids)
->get();
} else {
$this->error('Specify --all or --id=<id>');
return 1;
}

$bar = $this->output->createProgressBar($customers->count());
$bar->start();

$success = 0;
$failed = 0;

foreach ($customers as $customer) {
if ($customer->user) {
$result = $syncService->syncCustomer($customer, $customer->user);

if ($result['success']) {
$success++;
} else {
$failed++;
$this->newLine();
$this->warn("Failed: Customer {$customer->id} - {$result['error']}");
}
}

$bar->advance();
}

$bar->finish();
$this->newLine(2);

$this->info("Synced: {$success} | Failed: {$failed}");

return $failed > 0 ? 1 : 0;
}
}

Usage:

# Sync all active customers
php artisan customers:sync-cloud --all

# Sync specific customers
php artisan customers:sync-cloud --id=123 --id=456

Sync Behavior

New Customer

When a customer doesn't exist in AsaHome Cloud (based on sync_id or email):

  1. New user account is created
  2. Random password is generated (user must reset)
  3. sync_id links to Laravel customer ID

Existing Customer

When a customer already exists:

  1. User details are updated (name, active status, etc.)
  2. Email changes are handled if sync_id matches
  3. MRR and other metadata are updated

Conflict Resolution

ScenarioBehavior
Same sync_id, different emailUpdate email
Same email, different sync_idError (conflict)
Same email, no sync_id in cloudLink accounts

Error Handling

Retry Logic

Implement exponential backoff for transient failures:

use Illuminate\Support\Facades\Http;

$response = Http::retry(3, 100, function ($exception, $request) {
// Retry on connection errors and 5xx responses
return $exception instanceof \Illuminate\Http\Client\ConnectionException
|| ($exception instanceof \Illuminate\Http\Client\RequestException
&& $exception->response->serverError());
})->withHeaders([
'X-Internal-Api-Key' => $this->apiKey,
])->post("{$this->baseUrl}/auth/sync-customer", $data);

Queue Failed Syncs

Use Laravel queues for reliability:

<?php

namespace App\Jobs;

use App\Models\Customer;
use App\Services\CustomerSyncService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SyncCustomerJob implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;

public int $tries = 3;
public int $backoff = 60;

public function __construct(
public Customer $customer
) {}

public function handle(CustomerSyncService $syncService): void
{
$result = $syncService->syncCustomer(
$this->customer,
$this->customer->user
);

if (!$result['success']) {
throw new \Exception($result['error']);
}
}
}

Security Considerations

  1. API Key Rotation: Periodically rotate internal API keys
  2. HTTPS Only: Always use TLS for sync requests
  3. IP Whitelisting: Optionally restrict sync endpoint to known IPs
  4. Audit Logging: Log all sync operations for compliance
  5. Rate Limiting: Prevent abuse of sync endpoint

Next Steps