If you want to know what is actually happening to your traffic on SFCC, the eCDN logs are where the truth lives. Who hit your site, from which ASN, with what bot score, which firewall rule fired. You can analyze it after the fact, or you can act on it. The hot topic right now is telling bot traffic apart from real shoppers, and these logs are the raw material for exactly that. You can even automate firewall rules based on them, but that is a whole other article (coming soon).

Today let's keep it simple: how to get those logs out of the eCDN and into an S3 bucket with a Logpush job.

I recorded a full walkthrough where I set one up on a sandbox using a free-tier AWS account. Setup to first log in under 16 minutes.

The good news: there is finally a UI

For a long time Logpush jobs could only be created through the API. As of a release or two ago, there is now a proper UI for it inside Business Manager, under Administration → Embedded CDN → Settings. On top of that, eCDN is now available on sandboxes, which means you can safely test all of this on a sandbox before you touch anything that matters.

Here is the shape of what we are building:

flowchart LR
    U[Shopper] --> E[SFCC eCDN
Cloudflare] E --> O[SFCC Origin] E -. logs .-> LP[Logpush job] LP --> S3[(Amazon S3)]

The eCDN sees every request, and Logpush quietly ships a copy of the log lines to your bucket. That is the whole idea.

First rule of Logpush: you only get two jobs

This is the constraint that shapes everything else, so let's get it out of the way early. You get a maximum of two Logpush jobs per zone. Two. That's it.

So spend them wisely. The split that makes the most sense:

flowchart TB
    Z[Your zone] --> J1[Job 1
HTTP requests] Z --> J2[Job 2
Firewall events] J1 --> A1[General traffic analysis
bot scores, ASNs, paths] J2 --> A2[Which firewall rules actually fire]
  • Job 1 for HTTP requests. This is your general traffic feed. Bot scores, client IPs, ASNs, paths, user agents, the works.
  • Job 2 for firewall events. This tells you which of your firewall rules are actually being triggered and what action was taken. That matters more than it sounds, because there is a hard limit of 500 firewall rules. A firewall-events job lets you see which rules earn their keep, so you can prune the ones that never fire and free up room.

Alright, let's build one.

Creating the job (the happy path)

New Logpush job. Pick your destination (I'm using Amazon S3, but pick whatever fits your stack). Then select the dataset: HTTP requests or Firewall events. We'll start with HTTP requests.

Point it at your bucket and give it a path. Small tip that saves future-you a headache: use the {DATE} placeholder in the path. Logpush creates a lot of files, and dumping them all into one flat folder gets messy fast. Something like logs/{DATE} keeps each day tidy.

Set the region to match your S3 bucket exactly (mine is Ohio). If these do not match, nothing works.

The two gotchas that will get you

Everything above is smooth. These two are where people (me included) lose ten minutes.

Gotcha 1: the ownership challenge hates the date placeholder

The UI hands you an AWS resource policy. Copy it, go to your bucket → Permissions → Bucket policy, and paste it in. It saves cleanly, no errors, looks great.

Then you hit Next and get "Failed to send ownership challenge."

The reason is that the S3 side does not accept the {DATE} placeholder during the ownership challenge step. Fix: edit the path, remove the placeholder, and try again. This time the challenge goes through and drops a small challenge file into your bucket. Open it, copy the token, paste it back into the UI, confirm, and you own the destination.

flowchart LR
    A[Paste bucket policy] --> B{Hit Next}
    B -->|"{DATE} in path"| C[Failed to send
ownership challenge] C --> D[Remove placeholder
retry] D --> E[Grab token
from bucket] E --> F[Confirm ownership]

Gotcha 2: your test traffic never reaches the eCDN

You create the job, it says pending, and now you want to see it flip to active. The only way is to actually push some logs through, which means generating real traffic.

So you open the storefront and browse around. And nothing happens.

Here is why: hitting the normal storefront URL goes straight to origin and skips the eCDN entirely. Open dev tools, look at the response headers, and you'll see no Cloudflare headers at all. No eCDN, no logs.

For sandboxes you need to browse through the my. host (the *.my.commercecloud.salesforce.com one). That request actually routes through the eCDN, and now your logs start flowing. Browse a few pages and give it a minute.

Filters: store what you need, not everything

When you name the job you get to choose all logs or filtered logs. Filter. Please filter.

Storing everything is overwhelming and eats space fast. Filtering lets you keep only what you care about. In the demo I filtered on two fields:

  • ClientRequestHost contains my sandbox host
  • ClientRequestPath contains /s/RefArch

Drop the static assets

Here is the filter that saves you the most, and the one I'd add to almost every HTTP-requests job: throw away the static content.

Think about what you actually want from these logs — page visits, who's hitting what, bot scores, ASNs. You do not care that a shopper's browser also pulled down forty CSS, JS, image, and font files to render that one page. But the eCDN logs every one of them. In practice static assets are the overwhelming majority of your log lines, which means most of your storage (and most of the noise) is spent on requests you'll never look at.

The cleanest way to cut them in one shot is to filter on the response content type:

  • EdgeResponseContentType contains text/html

This keeps full page loads and the AJAX controller fragments that make up a real session — the Cart-MiniCartShow record above is text/html, so it survives — while dropping CSS, JS, images, and fonts, none of which carry that type. A BotScore on a .woff2 tells you nothing the parent page request didn't already, so you lose no signal.

Two things to keep in mind before you commit to it:

  • It also drops JSON responses (SCAPI/OCAPI and JSON controller endpoints). That's fine for a storefront-traffic view. If you're specifically hunting API abuse, don't use this filter — or widen it.
  • It drops redirects and typeless responses too. Minor, but worth knowing if 3xx chains matter to you.

You also pick which fields to store. Grab the ones that actually help you reason about traffic and skip the noise. My default set:

  • ClientASN, ClientIP, ClientIPClass (who and from where)
  • ClientRequestURI, ClientRequestUserAgent (what and with what)
  • BotScore (the good stuff, more below)
  • RayID (so you can correlate with everything else)

I leave Cookies off. You almost never want that sitting in a bucket.

What a log line looks like

Logs land compressed as .gz. Unzip one and each line is a JSON object. There are a lot of fields, but only a handful do the heavy lifting for traffic analysis. Here is a trimmed record with the ones that matter most highlighted:

{
  "BotScore": 98,
  "BotScoreSrc": "Machine Learning",
  "ClientASN": 14340,
  "ClientCountry": "us",
  "ClientIP": "123.123.123.123",
  "ClientIPClass": "noRecord",
  "ClientRequestHost": "abcd-123.my.commercecloud.salesforce.com",
  "ClientRequestURI": "/on/demandware.store/Sites-RefArch-Site/en_US/Cart-MiniCartShow",
  "ClientRequestUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ... Chrome/152.0.0.0 Safari/537.36",
  "EdgeResponseStatus": 200,
  "SecurityActions": ["log"],
  "SecurityRuleDescription": "Log Rule",
  "RayID": "a3caa7730c129682"
}
Show the full record — every field the eCDN captured
{
  "BotDetectionIDs": [],
  "BotScore": 98,
  "BotScoreSrc": "Machine Learning",
  "BotTags": [],
  "CacheCacheStatus": "dynamic",
  "CacheReserveUsed": false,
  "CacheResponseBytes": 4606,
  "ClientASN": 14340,
  "ClientCountry": "us",
  "ClientDeviceType": "desktop",
  "ClientIP": "123.123.123.123",
  "ClientIPClass": "noRecord",
  "ClientRequestBytes": 7798,
  "ClientRequestHost": "abcd-123.my.commercecloud.salesforce.com",
  "ClientRequestMethod": "GET",
  "ClientRequestPath": "/on/demandware.store/Sites-RefArch-Site/en_US/Cart-MiniCartShow",
  "ClientRequestProtocol": "HTTP/2",
  "ClientRequestReferer": "https://abcd-123.my.commercecloud.salesforce.com/s/RefArch/checked-silk-tie-25752235M.html?lang=en_US",
  "ClientRequestScheme": "https",
  "ClientRequestSource": "eyeball",
  "ClientRequestURI": "/on/demandware.store/Sites-RefArch-Site/en_US/Cart-MiniCartShow",
  "ClientRequestUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36",
  "ClientSSLCipher": "ECDHE-ECDSA-AES128-GCM-SHA256",
  "ClientSSLProtocol": "TLSv1.2",
  "ClientTCPRTTMs": 5,
  "Cookies": {},
  "EdgeCFConnectingO2O": false,
  "EdgeColoCode": "IAD",
  "EdgeColoID": 1024,
  "EdgeEndTimestamp": "2026-09-17T19:52:55Z",
  "EdgePathingOp": "wl",
  "EdgePathingSrc": "undef",
  "EdgePathingStatus": "nr",
  "EdgeRequestHost": "abcd-123.my.commercecloud.salesforce.com",
  "EdgeResponseBodyBytes": 1758,
  "EdgeResponseBytes": 2636,
  "EdgeResponseCompressionRatio": 4.4920363,
  "EdgeResponseContentType": "text/html;charset=UTF-8",
  "EdgeResponseStatus": 200,
  "EdgeServerIP": "104.23.209.15",
  "EdgeStartTimestamp": "2026-09-17T19:52:55Z",
  "EdgeTimeToFirstByteMs": 85,
  "OriginRequestHeaderSendDurationMs": 0,
  "OriginResponseDurationMs": 74,
  "OriginResponseHeaderReceiveDurationMs": 73,
  "OriginResponseStatus": 200,
  "OriginTCPHandshakeDurationMs": 0,
  "OriginTLSHandshakeDurationMs": 0,
  "ParentRayID": "00",
  "RayID": "a3caa7730c129682",
  "SecurityActions": ["log"],
  "SecurityRuleDescription": "Log Rule",
  "SecurityRuleIDs": ["b93e6d91300e4784b024c4181axxyyzz"],
  "SecuritySources": ["firewallCustom"],
  "ZoneName": "abcd-123.my.commercecloud.salesforce.com"
}

The star of the show is BotScore. Cloudflare scores traffic from 1 to 99. Close to 99 means almost certainly a human. Close to 1 means almost certainly a bot. The record above sits at 98, so, real person. Pair the score with ClientASN, ClientCountry, and ClientIP and you have the ingredients for automated firewall rules that block the obvious bots. That is the "act on your logs" part I keep teasing.

If you also run the firewall-events job, each record tells you the Action taken, the RuleID, and the rule Description, so you can watch your rules do their job in real time.

One thing nobody warns you about: client IP and stacked CDNs

Here is a subtle one worth knowing before you build anything on top of these logs.

The Logpush schema is a fixed list of fields. There is no field for arbitrary request headers, and ClientIP is always the IP that connected to the eCDN. Normally that is your shopper, and life is good.

But if you put another CDN in front of the eCDN (a stacked setup, say Fastly or Akamai out front), the eCDN only ever sees the front CDN's edge IP. So ClientIP in your Logpush output becomes that edge IP, not the real shopper.

flowchart LR
    S[Real shopper
1.2.3.4] --> F[Front CDN
Fastly / Akamai] F -->|"eCDN sees
the front CDN IP"| E[SFCC eCDN] E --> O[Origin] E -. Logpush .-> L[(ClientIP = front CDN,
not 1.2.3.4)]

And because the schema has no slot for a custom header, you cannot recover the real shopper IP from eCDN Logpush, no matter how you name headers upstream. Anything that depends on the true client IP (IP-based rate limiting, geo blocking, bot score) gets fuzzy in a stacked setup, because the eCDN is scoring the front CDN, not the shopper.

The practical takeaway: if you run a stacked CDN, capture real client IPs at the outermost layer (the one that actually sees the shopper first). Keep the eCDN logs for what the eCDN genuinely observed: rule hits, paths, RayIDs, and edge behavior. They are still great for that.

Wrapping up

That is the whole thing. Two jobs per zone, one for HTTP requests and one for firewall events, filtered down to what you care about, landing in S3. Watch out for the date placeholder during the ownership challenge, and remember to send your test traffic through the my. host so it actually hits the eCDN.

Happy logging. 📦