| Server IP : 35.80.110.71 / Your IP : 216.73.216.221 Web Server : Apache/2.4.58 (Ubuntu) System : Linux ip-172-31-21-44 6.17.0-1019-aws #19~24.04.1-Ubuntu SMP Tue Jun 23 18:53:06 UTC 2026 x86_64 User : ubuntu ( 1000) PHP Version : 8.3.31 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/switchyard/current/app/Http/Controllers/ |
Upload File : |
<?php
namespace App\Http\Controllers;
use App\Jobs\MirrorClickUpStatusJob;
use App\Models\ClickUpConnection;
use App\Models\Scopes\WorkspaceScope;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
* Incoming ClickUp webhook receiver.
*
* URL: POST /webhooks/clickup/{workspace_id}
*
* ClickUp signs the request with `X-Signature: <hex>` computed as
* `hmac_sha256(workspace.webhook_secret, raw_body)`. We look up the
* per-workspace `webhook_secret` on `clickup_connections`, verify in
* constant time, then dispatch the payload to a queue job for processing.
*
* Processing itself (mapping event → lead status, emitting lead_events,
* updating mirrors) lives in MirrorClickUpStatusJob, added in Phase 1.8.
* Until then this controller verifies and acks but logs the payload.
*/
class ClickUpWebhookController extends Controller
{
public function __invoke(Request $request, string $workspaceId): JsonResponse
{
$connection = ClickUpConnection::withoutGlobalScope(WorkspaceScope::class)
->where('workspace_id', $workspaceId)
->first();
if (! $connection || ! $connection->webhook_secret) {
return response()->json(['ok' => false, 'reason' => 'unknown_workspace'], 404);
}
$signature = (string) $request->header('X-Signature', '');
$body = $request->getContent();
if ($signature === '' || ! hash_equals(
hash_hmac('sha256', $body, $connection->webhook_secret),
$signature,
)) {
return response()->json(['ok' => false, 'reason' => 'invalid_signature'], 401);
}
Log::info('ClickUp webhook received', [
'workspace_id' => $workspaceId,
'event' => $request->input('event'),
'task_id' => $request->input('task_id'),
]);
MirrorClickUpStatusJob::dispatch($workspaceId, $request->all());
return response()->json(['ok' => true]);
}
}