AWS integration

Lambda, EC2, or a container.
One call, and you're measuring.

Anywhere your backend handles a request, you can measure AI traffic. Fire one fire-and-forget call to Arrivl's intake from your handler, it never blocks your response.

Available today

One HTTP call from your request path, plus an environment variable for your key. Works on any AWS compute.

About 5 minutesNo proxy, no DNSFire-and-forgetAny AWS compute

Install guide

Install AWS, start to finish

Server-side integration for any AWS compute. Set ARRIVL_WEBSITE_KEY, then fire one fire-and-forget GET to the Arrivl intake endpoint from your request handler. Works on Lambda, EC2, Fargate, or any container running Node.js. Use this when your site is served from your own AWS backend and you want server-side measurement, which records requests from bots that do not run JavaScript.

01Install guide

Prerequisites

  • 01An Arrivl website key (Settings, + New Key, starts with ak_)
  • 02An AWS compute target: Lambda, EC2, Fargate, or any container running Node.js 18+
  • 03A request handler you can edit (Lambda handler, Express/Fastify middleware, or equivalent)
  • 04Outbound HTTPS allowed from the compute (a Lambda in a private VPC needs a NAT gateway for egress)

02Install guide

Setup

  1. Set the website key as an environment variable

    Add ARRIVL_WEBSITE_KEY to your environment. On Lambda, set it under Configuration > Environment variables. On Fargate/ECS, add it to the container definition in the task definition. On EC2, export it in the process environment or your service unit file. Do not hardcode the key in source.

    .env

    ARRIVL_WEBSITE_KEY=ak_YOUR_WEBSITE_KEY
  2. Fire one fire-and-forget GET from your handler

    Read url, userAgent, ref, and ip from the incoming request and send a single GET to the intake endpoint. Do not await it, so it never delays your response. Track HTML pages only; skip static assets. The example below works as a Lambda handler; the trackArrivl helper drops in unchanged as Express middleware.

    javascript

    // track.js: shared helper, no dependencies (Node 18+ global fetch)
    const INTAKE = 'https://arrivl.ai/api/v1/intake/pageview';
    
    export function trackArrivl({ url, userAgent, ref, ip }) {
      // intake requires a non-empty userAgent and ip; skip the send otherwise.
      if (!userAgent || !ip) return;
      const params = new URLSearchParams({
        url,
        userAgent,
        ref: ref || '',
        ip,
        websiteKey: process.env.ARRIVL_WEBSITE_KEY,
      });
      // Fire-and-forget: no await. .catch keeps a failed send silent.
      fetch(`${INTAKE}?${params}`).catch(() => {});
    }
    
    // --- AWS Lambda (API Gateway / Lambda URL, HTTP API v2 event) ---
    export const handler = async (event) => {
      const h = event.headers || {};
      const proto = h['x-forwarded-proto'] || 'https';
      // Externally-visible host. `host` is only the hostname the LAST hop used —
      // behind API Gateway, CloudFront, an ALB or any reverse proxy that's the
      // internal endpoint (<id>.execute-api.<region>.amazonaws.com, an ALB DNS
      // name, localhost), not your domain. Arrivl stores this hostname on every
      // event and verifies your install against it, so send the forwarded one
      // whenever there is one (leftmost value: proxies append). The second lookup
      // covers API Gateway REST, which preserves header case; HTTP API v2
      // lowercases everything.
      const fwdHost = h['x-forwarded-host'] || h['X-Forwarded-Host'];
      const host =
        fwdHost?.split(',')[0]?.trim() || h.host || h.Host || 'yoursite.com';
      const path = event.rawPath || event.path || '/';
      const qs = event.rawQueryString ? `?${event.rawQueryString}` : '';
    
      trackArrivl({
        url: `${proto}://${host}${path}${qs}`,
        userAgent: h['user-agent'],
        ref: h.referer || h.referrer,
        // Real client/bot IP: leftmost X-Forwarded-For, then the request source IP.
        ip:
          h['x-forwarded-for']?.split(',')[0]?.trim() ||
          event.requestContext?.http?.sourceIp,
      });
    
      // ... your real response ...
      return { statusCode: 200, headers: { 'content-type': 'text/html' }, body: '<!doctype html>...' };
    };
    
    // --- Express / Fastify on EC2 or Fargate ---
    // app.use((req, _res, next) => {
    //   if (req.method === 'GET' && req.accepts('html')) {
    //     // Same forwarded-host rule as the Lambda handler above: behind nginx, an
    //     // ALB or any container platform, req.get('host') is the internal address
    //     // and an event carrying it can never verify your install.
    //     const host = req.get('x-forwarded-host')?.split(',')[0]?.trim() || req.get('host');
    //     const proto = req.get('x-forwarded-proto')?.split(',')[0]?.trim() || req.protocol;
    //     trackArrivl({
    //       url: `${proto}://${host}${req.originalUrl}`,
    //       userAgent: req.get('user-agent'),
    //       ref: req.get('referer'),
    //       ip: req.get('x-forwarded-for')?.split(',')[0]?.trim() || req.ip,
    //     });
    //   }
    //   next();
    // });
  3. Deploy and load a page

    Deploy your function or container, then load any page on the live site so Arrivl receives its first event. That first real event flips your project to Connected; deploying the code alone is not enough. Behind an ALB or CloudFront, the X-Forwarded-For header carries the real client IP, so read the leftmost value as shown above.

03Install guide

How it works

  1. 01Your handler runs on every request and reads url, userAgent (User-Agent), ref (Referer), and ip from the incoming request.
  2. 02It sends one GET to the intake endpoint with those values plus your websiteKey, URL-encoded.
  3. 03The call is fire-and-forget: it is not awaited, so the response returns with no added latency, and a failed send is caught and ignored.
  4. 04Arrivl classifies the visit server-side at intake. Because measurement happens at the server, it records requests from AI bots that do not run JavaScript.
  5. 05Behind an ALB or CloudFront, the real client IP is the leftmost entry in X-Forwarded-For; the helper falls back to the request source IP when that header is absent.

04Install guide

Verify

Send a test request to the intake endpoint with your website key. A success returns {"ok": true}. Then open Agent Analytics; events appear within a few seconds.

bash

curl "https://arrivl.ai/api/v1/intake/pageview\
?url=https://yoursite.com/test\
&userAgent=Mozilla/5.0%20(compatible;%20GPTBot/1.0)\
&ref=\
&ip=1.2.3.4\
&websiteKey=ak_YOUR_WEBSITE_KEY"

# Should return: {"ok": true}

05Install guide

Troubleshooting

Still stuck? Write to hello@arrivl.ai.

01Events stop appearing once my Lambda response returns

On Lambda the runtime can freeze the execution environment the moment the handler returns, killing an in-flight fire-and-forget fetch. Send the GET before you return your response (as shown), so the request is already on the socket. If you still see drops on a low-traffic function, await the fetch inside a short Promise.race with a timeout; do not await it on the hot path of a busy service.

02Requests are not appearing in Agent Analytics at all

Confirm ARRIVL_WEBSITE_KEY is set in the function or task environment and is not the placeholder. Run the verify curl: it must return {"ok": true}. If the curl works but your handler does not, your compute likely has no outbound HTTPS; a Lambda in a private VPC subnet needs a NAT gateway to reach the public intake endpoint. Note the helper skips the send when the request has no User-Agent or no resolvable client IP, since intake requires both.

03Every visitor shows the same IP

Your handler is reading the ALB or CloudFront IP, not the visitor. Behind a load balancer or CDN, the real client IP is the leftmost value in X-Forwarded-For. Read x-forwarded-for and split on the comma, taking the first entry; fall back to the request source IP only when that header is absent.

Values shown as ak_YOUR_WEBSITE_KEY are placeholders. Sign in and the in-dashboard version of this guide fills in your real key and per-project values for you.

Next step

Add one call and see the AI traffic you're missing

See all the ways to connect →