Creating Webhooks (HTTP Triggers)

Syncing once per day (or once per hour) is a start, but it is much better if jobs are triggered as soon as a record is updated in the source system. A common way to handle that is to have the source application trigger the job via a HTTP request (a webhook).

These are not included in the package, but here is how to write one...

In routes/api.php, define a route for each webhook:

Route::withoutMiddleware('throttle:api')->group(function () {
    Route::post('sync-triggers/crm-accounts', [SyncTriggerController::class, 'crmAccounts']);
    //...
});

Note: We disable throttling so that we don't miss any changes.

Then create a controller & action such as this:

class SyncTriggerController extends Controller
{
    private function auth(string $username, string $password): void
    {
        $request = request();

        if ($request->getUser() !== $username || !hash_equals($password, $request->getPassword())) {
            throw new UnauthorizedHttpException('Basic', 'Invalid credentials.');
        }
    }

    public function crmAccounts(Request $request): void
    {
        $this->auth('crm', config('custom.crm_api_token'));

        $request->validate([
            'trigger' => ['nullable', 'string'],
            'record_id' => ['nullable', 'string', 'max:255'],
            'record_name' => ['nullable', 'string'],
        ]);

        $trigger = $request->trigger ?? 'API call from ' . $request->getClientIp();

        SyncAccountsFromCrmToPasswordsDatabase::dispatch($trigger, $request->record_id, $request->record_name);
        SyncAccountsFromCrmToSupportCases::dispatch($trigger, $request->record_id, $request->record_name);
        SyncAccountsFromCrmToKbClientPages::dispatch($trigger, $request->record_id, $request->record_name);
        SyncAccountsFromCrmToTimesheets::dispatch($trigger, $request->record_id, $request->record_name);
    }
}

(TODO: Move the auth() method into a trait/helper/middleware in Laravel Utilities?)

In this example we are manually authenticating the request against a password set in .env / config/custom.php. Another option is to create users in the database and use Laravel's built-in token authentication driver (or the new Sanctum package).

To trigger a webhook from another Laravel application, do something like this:

// TODO: Untested
$url = config('custom.sync_api_uri');

try {
    HTTP::acceptJson()
        ->timeout(2)
        ->withBasicAuthentication('crm', config('custom.sync_api_token'))
        ->post($url, [
            'trigger' => "{$user->name} editing account '{$account->name}'",
            'record_id' => $account->id,
            'record_name' => $account->name,
        ]);
} catch (HttpClientException $e) {
    Log::warn("Failed to notify $url: {$e->getMessage()}");
}

For a plain PHP example using cURL, see the Alberon CRM.

For webhooks from third-party applications, you may need to read their documentation on how to authenticate requests and what format the request will take.

Warning: You should not allow unauthenticated requests, as it could potentially be used in a denial-of-service attack (to overload the server).