touchestate/php-sdk

Official PHP SDK for TouchEstate API - works with Laravel and standalone PHP

Maintainers

Package info

github.com/innovayse/touchestate-php-sdk

Homepage

Documentation

pkg:composer/touchestate/php-sdk

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-02-18 12:55 UTC

This package is auto-updated.

Last update: 2026-03-21 07:35:48 UTC


README

Official PHP SDK for the TouchEstate API with TE-SigV4 authentication.

PHP Version Laravel Tests License

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

  1. Log in to TouchEstate Dashboard
  2. Navigate to SettingsAccess Keys
  3. Click Create Access Key
  4. 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:

  1. Create new keys in TouchEstate Dashboard
  2. Update environment variables on all servers
  3. Test the new keys work correctly
  4. 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:

  1. Revoke immediately in the dashboard
  2. Generate new replacement keys
  3. Update environment variables on all servers
  4. Review recent activity for unauthorized access
  5. 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 (ntpdate or 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

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!