touchestate / php-sdk
Official PHP SDK for TouchEstate API - works with Laravel and standalone PHP
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
README
Official PHP SDK for the TouchEstate API with TE-SigV4 authentication.
About
TouchEstate SDK is a PHP library that provides seamless integration with the TouchEstate CRM API. It handles all the complexity of cryptographic request signing, authentication, and API communication, allowing you to focus on building your real estate application.
Key Features
- ✅ TE-SigV4 Authentication - Cryptographic request signing using HMAC-SHA256 for maximum security
- ✅ Laravel Integration - First-class support with service provider, facade, and dependency injection
- ✅ Standalone Usage - Works with any PHP project, not just Laravel
- ✅ Type-Safe - Full PHP 8.1+ type hints and strict types for better IDE support
- ✅ Clean API - Stripe-inspired fluent interface for intuitive usage
- ✅ Comprehensive Error Handling - Specific exceptions for different error scenarios
- ✅ Retry Logic Ready - Built-in support for retry patterns and circuit breakers
- ✅ Production Ready - Thoroughly tested with 57+ unit and integration tests
- ✅ Well Documented - Extensive inline documentation and usage examples
What is TE-SigV4?
TE-SigV4 (TouchEstate Signature Version 4) is a cryptographic authentication protocol used by TouchEstate API to ensure maximum security for all API requests.
Why TE-SigV4?
Traditional API authentication methods (API tokens, basic auth) have significant security weaknesses:
- Tokens can be intercepted during transmission
- No protection against tampering - attackers can modify request data
- No replay protection - captured requests can be re-sent
- Single point of failure - one leaked token compromises everything
TE-SigV4 solves all these problems through cryptographic request signing.
How It Works
Every API request is cryptographically signed using HMAC-SHA256:
1. Client calculates SHA-256 hash of request body
2. Client builds canonical request (method, path, headers, body hash)
3. Client signs the canonical request with secret key
4. Client sends request with signature in Authorization header
5. Server independently calculates signature using same algorithm
6. Server compares signatures - if they match, request is authentic
Your secret key never leaves your server - only the signature is transmitted.
Security Guarantees
- Request Authenticity - Cryptographically proves the request came from you (only you have the secret key)
- Request Integrity - Any modification to the request (headers, body, URL) invalidates the signature
- Replay Protection - 5-minute timestamp window prevents captured requests from being re-sent
- Credential Scoping - Signatures are scoped to date/service, limiting damage if compromised
- Zero Credential Exposure - Secret keys are never transmitted over the network
Algorithm Details
TE-SigV4 uses industry-standard HMAC-SHA256 cryptography with key derivation:
- Algorithm: TE-HMAC-SHA256
- Scope:
{date}/global/touchestate/te_request - Header:
TE-HMAC-SHA256 Credential=..., SignedHeaders=..., Signature=... - Region:
global(single worldwide region) - Service:
touchestate(TouchEstate CRM only)
Why This Matters for Developers
✅ Automatic signing - The SDK handles all signature calculation for you ✅ Maximum security - Bank-grade cryptographic authentication for your real estate data ✅ Simple usage - Just provide your TouchEstate keys and make requests ✅ Zero complexity - No manual cryptography or signature logic needed
// All signature calculation happens automatically $properties = TouchEstate::properties()->list(); // Behind the scenes: // 1. SDK calculates body hash // 2. SDK builds canonical request // 3. SDK signs with your secret key // 4. SDK adds Authorization header // 5. SDK sends signed request // You don't see any of this complexity!
Installation
Install via Composer:
composer require touchestate/php-sdk
Requirements
- PHP 8.1 or higher
- Laravel 10.x or 11.x (for Laravel integration)
- GuzzleHTTP 7.x
Quick Start
1. Get Your API Keys
- Log in to TouchEstate Dashboard
- Navigate to Settings → Access Keys
- Click Create Access Key
- Copy both keys (secret key shown only once!)
2. Configure Environment
Add your credentials to .env:
TOUCHESTATE_PUBLIC_KEY=pk_live_your_public_key_here TOUCHESTATE_SECRET_KEY=sk_live_your_secret_key_here TOUCHESTATE_BASE_URL=https://api.touchestate.am # Disable SSL verification only for local development on Windows # Never set to false in production TOUCHESTATE_VERIFY_SSL=true
3. Start Using
use TouchEstate\Sdk\Facades\TouchEstate; // List properties $properties = TouchEstate::properties()->list([ 'page' => 1, 'pageSize' => 20, ]); foreach ($properties['items'] as $property) { echo $property['title'] . ' - $' . number_format($property['price']) . "\n"; } // Get single property $property = TouchEstate::properties()->retrieve('property-id'); echo $property['title'];
Laravel Integration
Service Provider (Auto-registered)
The package automatically registers its service provider in Laravel 10+.
Publish Configuration
php artisan vendor:publish --tag=touchestate-config
This creates config/touchestate.php:
return [ 'public_key' => env('TOUCHESTATE_PUBLIC_KEY'), 'secret_key' => env('TOUCHESTATE_SECRET_KEY'), 'base_url' => env('TOUCHESTATE_BASE_URL', 'https://api.touchestate.am'), 'timeout' => env('TOUCHESTATE_TIMEOUT', 30), 'connect_timeout' => env('TOUCHESTATE_CONNECT_TIMEOUT', 10), 'verify_ssl' => env('TOUCHESTATE_VERIFY_SSL', true), ];
Usage Methods
1. Facade (Recommended)
use TouchEstate\Sdk\Facades\TouchEstate; $properties = TouchEstate::properties()->list(); $contacts = TouchEstate::contacts()->list();
2. Dependency Injection
use TouchEstate\Sdk\TouchEstateClient; class PropertyController extends Controller { public function __construct( private TouchEstateClient $client ) {} public function index() { $properties = $this->client->properties()->list([ 'page' => 1, 'pageSize' => 20, ]); return view('properties.index', compact('properties')); } }
3. Service Container
$client = app('touchestate'); $properties = $client->properties()->list();
Standalone Usage (Without Laravel)
use TouchEstate\Sdk\TouchEstateClient; $client = new TouchEstateClient([ 'public_key' => 'pk_live_xxx', 'secret_key' => 'sk_live_xxx', 'base_url' => 'https://api.touchestate.am', ]); $properties = $client->properties()->list();
API Reference
Properties Service
List Properties
$properties = $client->properties()->list([ 'page' => 1, // Page number (default: 1) 'pageSize' => 20, // Items per page (max: 100) ]);
Response:
[
'items' => [
[
'id' => 'uuid',
'title' => 'Luxury Apartment in Yerevan',
'price' => 500000,
'propertyType' => 'Apartment',
'listingType' => 'Sale',
'bedrooms' => 3,
'bathrooms' => 2,
'area' => 120.5,
'address' => [...],
// ... more fields
],
],
'pageNumber' => 1,
'pageSize' => 20,
'totalCount' => 150,
'totalPages' => 8,
'hasNextPage' => true,
'hasPreviousPage' => false,
]
Retrieve Single Property
$property = $client->properties()->retrieve('property-id');
Contacts Service
List Contacts
$contacts = $client->contacts()->list([ 'page' => 1, 'pageSize' => 50, ]);
Response:
[
'items' => [
[
'id' => 'uuid',
'firstName' => 'John',
'lastName' => 'Doe',
'email' => 'john@example.com',
'phone' => '+1234567890',
'type' => 'Lead',
// ... more fields
],
],
'pageNumber' => 1,
'pageSize' => 50,
'totalCount' => 500,
'totalPages' => 10,
]
Retrieve Single Contact
$contact = $client->contacts()->retrieve('contact-id');
Workspace Service
Get Workspace Information
Get information about your workspace (determined from your API key):
$workspace = $client->workspace()->retrieve(); // Or use the alias method $workspace = $client->workspace()->info();
Response:
[
'id' => 'uuid',
'name' => 'My Real Estate Agency',
'slug' => 'my-agency',
'logoUrl' => 'https://cdn.touchestate.am/logos/...',
'description' => 'Leading real estate agency in Yerevan',
'timezone' => 'Asia/Yerevan',
'defaultCommissionPercent' => 5.0,
'isActive' => true,
'createdAt' => '2024-01-01T00:00:00Z',
]
Use Cases:
- Display workspace branding in external applications
- Sync workspace settings and configuration
- Validate workspace status and permissions
- Show workspace details in CRM integrations
Error Handling
The SDK provides specific exception types for different error scenarios:
Exception Hierarchy
TouchEstateException (base)
├── AuthenticationException (401)
└── ApiException (API errors)
├── InvalidRequestException (4xx)
├── RateLimitException (429)
└── ServerException (5xx)
Basic Error Handling
use TouchEstate\Sdk\Exception\AuthenticationException; use TouchEstate\Sdk\Exception\InvalidRequestException; use TouchEstate\Sdk\Exception\RateLimitException; use TouchEstate\Sdk\Exception\ServerException; try { $properties = $client->properties()->list(); } catch (AuthenticationException $e) { // Invalid credentials - check your keys Log::error('Authentication failed: ' . $e->getMessage()); } catch (InvalidRequestException $e) { // Bad request, not found, validation error Log::warning('Invalid request: ' . $e->getMessage()); $details = $e->getResponseData(); // Get API error details } catch (RateLimitException $e) { // Too many requests - implement backoff Log::warning('Rate limited, retrying later'); } catch (ServerException $e) { // Server error - retry with backoff Log::error('Server error: ' . $e->getMessage()); }
Retry Logic Example
function retryableRequest(callable $request, int $maxRetries = 3): mixed { for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { try { return $request(); } catch (RateLimitException | ServerException $e) { if ($attempt === $maxRetries) { throw $e; } // Exponential backoff: 1s, 2s, 4s $delay = pow(2, $attempt - 1); sleep($delay); } } } // Usage $properties = retryableRequest(function() use ($client) { return $client->properties()->list(['page' => 1]); });
Getting Error Details
try { $property = $client->properties()->retrieve('invalid-id'); } catch (InvalidRequestException $e) { // HTTP status code echo $e->getCode(); // 404 // Error message (auto-extracted from API response) echo $e->getMessage(); // "Resource not found" // Full response body echo $e->getResponseBody(); // Decoded response data $data = $e->getResponseData(); if ($data) { print_r($data); } }
Advanced Usage
Custom HTTP Client
use TouchEstate\Sdk\HttpClient\GuzzleClient; $httpClient = new GuzzleClient([ 'timeout' => 60, 'connect_timeout' => 30, 'verify' => true, ]); $client = new TouchEstateClient([ 'public_key' => 'pk_live_xxx', 'secret_key' => 'sk_live_xxx', 'http_client' => $httpClient, ]);
Custom Headers
use TouchEstate\Sdk\Util\RequestOptions; $options = new RequestOptions([ 'X-Request-ID' => 'unique-request-id', ]); $properties = $client->properties()->list(['page' => 1], $options);
Environment-Specific Configuration
// config/touchestate.php return [ 'public_key' => env('TOUCHESTATE_PUBLIC_KEY'), 'secret_key' => env('TOUCHESTATE_SECRET_KEY'), 'base_url' => env('APP_ENV') === 'production' ? 'https://api.touchestate.am' : 'http://localhost:5100', 'timeout' => 30, ];
Logging
The SDK integrates with Laravel's logging system (PSR-3 compatible) to provide detailed insights into API requests, responses, and errors.
Automatic Logging
When using Laravel, the SDK automatically logs to your configured logger:
use TouchEstate\Sdk\Facades\TouchEstate; // SDK automatically logs: // [INFO] TouchEstate API request { method, path, params_count } // [INFO] TouchEstate API response { method, path, status, duration_ms } $properties = TouchEstate::properties()->list(['page' => 1]);
Log Levels
- INFO: API requests and successful responses
- WARNING: Rate limiting (429)
- ERROR: Authentication failures (401), client errors (4xx), server errors (5xx)
Basic Error Handling with Logging
use TouchEstate\Sdk\Facades\TouchEstate; use TouchEstate\Sdk\Exception\AuthenticationException; use TouchEstate\Sdk\Exception\RateLimitException; use TouchEstate\Sdk\Exception\ServerException; use Illuminate\Support\Facades\Log; try { // INFO: TouchEstate API request $properties = TouchEstate::properties()->list(['page' => 1]); // INFO: TouchEstate API response { status: 200, duration_ms: 125 } Log::info('Properties fetched successfully', [ 'count' => $properties['totalCount'], ]); } catch (AuthenticationException $e) { // ERROR: TouchEstate API authentication failed (already logged by SDK) Log::critical('API authentication broken - check keys', [ 'user_id' => auth()->id(), ]); return response()->json(['error' => 'Service unavailable'], 503); } catch (RateLimitException $e) { // WARNING: TouchEstate API rate limit exceeded (already logged by SDK) Log::warning('Rate limit hit - implementing backoff', [ 'user_id' => auth()->id(), ]); return response()->json(['error' => 'Too many requests'], 429); } catch (ServerException $e) { // ERROR: TouchEstate API server error (already logged by SDK) Log::error('API server error - will retry', [ 'error' => $e->getMessage(), ]); return response()->json(['error' => 'Service temporarily unavailable'], 503); }
Advanced: Retry Logic with Logging
use TouchEstate\Sdk\Exception\RateLimitException; use TouchEstate\Sdk\Exception\ServerException; use Illuminate\Support\Facades\Log; function fetchPropertiesWithRetry($client, $params, $maxRetries = 3): array { $attempt = 0; while ($attempt < $maxRetries) { $attempt++; try { Log::info('Fetching properties', [ 'attempt' => $attempt, 'max_attempts' => $maxRetries, ]); $properties = $client->properties()->list($params); // SUCCESS Log::info('Properties fetched successfully', [ 'attempt' => $attempt, 'count' => $properties['totalCount'], ]); return $properties; } catch (RateLimitException | ServerException $e) { // WARNING/ERROR already logged by SDK if ($attempt === $maxRetries) { Log::error('All retry attempts exhausted', [ 'attempts' => $maxRetries, 'last_error' => $e->getMessage(), ]); throw $e; } // Exponential backoff: 1s, 2s, 4s $delay = pow(2, $attempt - 1); Log::warning('Retrying after delay', [ 'attempt' => $attempt, 'delay_seconds' => $delay, 'error' => $e->getMessage(), ]); sleep($delay); } } } // Usage try { $properties = fetchPropertiesWithRetry($client, ['page' => 1]); } catch (Exception $e) { return response()->json(['error' => 'Failed to fetch properties'], 500); }
Custom Log Channel
Create a dedicated channel for TouchEstate logs:
// config/logging.php 'channels' => [ 'touchestate' => [ 'driver' => 'daily', 'path' => storage_path('logs/touchestate.log'), 'level' => env('APP_ENV') === 'production' ? 'warning' : 'debug', 'days' => 14, ], ],
Use the custom channel:
use Illuminate\Support\Facades\Log; $client = new TouchEstateClient([ 'public_key' => config('touchestate.public_key'), 'secret_key' => config('touchestate.secret_key'), 'logger' => Log::channel('touchestate'), ]);
Example Log Output
[2026-02-15 15:30:00] production.INFO: TouchEstate API request {"method":"GET","path":"/api/external/properties","params_count":2}
[2026-02-15 15:30:00] production.INFO: TouchEstate API response {"method":"GET","path":"/api/external/properties","status":200,"duration_ms":125.43}
[2026-02-15 15:30:00] production.INFO: Properties fetched successfully {"count":62}
Testing
The SDK includes comprehensive tests:
# Run all tests vendor/bin/phpunit # Run with coverage vendor/bin/phpunit --coverage-html coverage # Run specific test suite vendor/bin/phpunit tests/Unit vendor/bin/phpunit tests/Integration # Pretty output vendor/bin/phpunit --testdox
Test Coverage: 57 tests, 89 assertions, 100% coverage of core functionality.
Security Best Practices
1. Never Commit Secrets
CRITICAL: Never commit actual API keys to version control.
# ❌ NEVER commit files with actual secrets git add .env git add .env.production git add .env.local # ✅ ALWAYS keep .env files in .gitignore # .env files should never be tracked by Git
Proper Setup:
# .env (NEVER committed - contains actual secrets) TOUCHESTATE_PUBLIC_KEY=pk_live_abc123... TOUCHESTATE_SECRET_KEY=sk_live_xyz789... TOUCHESTATE_BASE_URL=https://api.touchestate.am
// config/touchestate.php (committed - contains env() references only) return [ 'public_key' => env('TOUCHESTATE_PUBLIC_KEY'), // ✅ Safe to commit 'secret_key' => env('TOUCHESTATE_SECRET_KEY'), // ✅ Safe to commit 'base_url' => env('TOUCHESTATE_BASE_URL', 'https://api.touchestate.am'), ];
2. Use Environment Variables
Never hardcode credentials in your code:
// ❌ NEVER hardcode keys $client = new TouchEstateClient([ 'public_key' => 'pk_live_abc123', 'secret_key' => 'sk_live_xyz789', ]); // ✅ ALWAYS use environment variables $client = new TouchEstateClient([ 'public_key' => env('TOUCHESTATE_PUBLIC_KEY'), 'secret_key' => env('TOUCHESTATE_SECRET_KEY'), ]); // ✅ Or use Laravel config (recommended) $client = new TouchEstateClient([ 'public_key' => config('touchestate.public_key'), 'secret_key' => config('touchestate.secret_key'), ]);
3. Use Different Keys Per Environment
Each environment should have its own keys:
# .env.production (production server only) TOUCHESTATE_PUBLIC_KEY=pk_live_production_key TOUCHESTATE_SECRET_KEY=sk_live_production_key # .env.staging (staging server only) TOUCHESTATE_PUBLIC_KEY=pk_test_staging_key TOUCHESTATE_SECRET_KEY=sk_test_staging_key # .env (local development only) TOUCHESTATE_PUBLIC_KEY=pk_test_dev_key TOUCHESTATE_SECRET_KEY=sk_test_dev_key
4. Rotate Keys Regularly
Generate new keys periodically and revoke old ones:
- Create new keys in TouchEstate Dashboard
- Update environment variables on all servers
- Test the new keys work correctly
- Revoke old keys in the dashboard
5. Monitor Key Usage
Regularly check key usage in the TouchEstate dashboard:
- Last used timestamp
- Request volume
- Any suspicious activity
6. Revoke Compromised Keys Immediately
If a key is leaked or compromised:
- Revoke immediately in the dashboard
- Generate new replacement keys
- Update environment variables on all servers
- Review recent activity for unauthorized access
- Investigate how the leak occurred
Troubleshooting
Authentication Fails (401)
Possible causes:
- Invalid public/secret key
- Key has expired
- Signature mismatch (clock skew)
Solutions:
- Verify keys are correct in
.env - Check key status in dashboard
- Ensure server time is synchronized (
ntpdateor NTP service)
Rate Limit Exceeded (429)
Solutions:
- Implement exponential backoff
- Cache frequently accessed data
- Reduce request frequency
- Consider upgrading plan if needed
Connection Timeout
Solutions:
- Increase timeout in config
- Check network connectivity
- Verify API endpoint URL
Invalid Response Format
Solutions:
- Update SDK to latest version
- Check API compatibility
- Verify base URL is correct
Support
- Documentation: https://docs.touchestate.am
- Dashboard: https://app.touchestate.am
- Issues: https://github.com/innovayse/touchestate-php-sdk/issues
- Source Code: https://github.com/innovayse/touchestate-php-sdk
- Email: support@touchestate.am
Changelog
1.0.0 (2026-02-15)
- Initial release
- TE-SigV4 authentication
- Properties API support
- Contacts API support
- Laravel integration
- Comprehensive error handling
- 100% test coverage
License
The MIT License (MIT). Please see License File for more information.
Credits
Developed by TouchEstate Team.
Ready to build amazing real estate applications? Get your API keys now!