DEV3 Technologies home

Queued click tracking with user-agent filter

Redirect instantly and record the click in a queued job, flagging crawlers by user agent so they never inflate your counts.

LaravelQueue jobsanalyticsqueueredirectbots

Code

<?php

namespace App\Http\Controllers;

use App\Jobs\RecordClick;
use App\Models\Link;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class RedirectController extends Controller
{
    public function __invoke(Request $request, string $code): RedirectResponse
    {
        $link = Link::where('code', $code)->firstOrFail();

        // Hand everything the job needs over as plain strings; the response is not delayed.
        if (! $request->isMethod('HEAD')) {
            RecordClick::dispatch([
                'link_id' => $link->id,
                'ua' => (string) $request->userAgent(),
                'ip_hash' => hash('sha256', $request->ip().config('app.key')),
                'referrer' => (string) $request->headers->get('referer'),
                'at' => now()->toIso8601String(),
            ])->onQueue('clicks');
        }

        return redirect()->away($link->url, 302);
    }
}

How it works

What it solves

A redirect endpoint is the hottest path in a link shortener. Writing a click row, looking up geo data and parsing the user agent inside the request makes every visitor wait, and link preview bots from chat apps and search engines quietly double your statistics.

How it works

RedirectController does the minimum: find the link, dispatch RecordClick with a small array payload, and return a 302. The payload carries only strings, so serialisation is cheap and nothing depends on the request object.

The job runs isBot() over the stored user agent. A short marker list catches the common crawlers, headless browsers and HTTP libraries, and an empty user agent counts as a bot too. Bot hits are still saved with is_bot = true, which keeps an audit trail and lets you tune the list later, but the counter increment only happens for humans. Unique visitor tracking can reuse the same payload.

The IP address is hashed before it is stored, so the table never holds raw visitor addresses.

Adapting it

Add markers for your own traffic, move the list to config, or replace the substring test with a maintained detector package. Send the job to a dedicated clicks queue so a backlog never delays other work.

Pitfalls

  • HEAD requests are almost always previews; skip tracking for them.
  • User agent lists age quickly; treat this as a filter, not a guarantee.

How to use it

  1. Add clicks (link_id, is_bot, ip_hash, ua, referrer, created_at) and a clicks_count column on links.
  2. Copy the controller and job.
  3. Route it last: Route::get('/{code}', RedirectController::class).
  4. Run a worker: php artisan queue:work --queue=clicks,default.
  5. Count only humans with Click::where('is_bot', false).

Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.

Related snippets