The June 2026 CFDI Catalog Update (c_NumPedimentoAduana): Your Code Doesn't Change — But Your Pipeline Must — Cesar Ayala
← All posts

The June 2026 CFDI Catalog Update (c_NumPedimentoAduana): Your Code Doesn't Change — But Your Pipeline Must

On June 19, 2026 the SAT published a CFDI 4.0 catalog update adding new mandatory entries to c_NumPedimentoAduana, the customs-pedimento catalog used in comercio-exterior invoicing. The XSD schemas were NOT modified, so your integration code doesn't change. You only need the refreshed catalog data — but if your pipeline hardcodes catalogs, your PAC will start rejecting silently.

What changed on June 19, 2026?

On June 19, 2026 the SAT published a CFDI 4.0 catalog update adding new mandatory records to c_NumPedimentoAduana, the catálogo (catalog) of customs-pedimento numbers used in comercio exterior (foreign-trade) invoicing. It affects CFDIs for first-hand sales of imported goods that reference a pedimento. Critically, the XSD schemas did not change — so your code stays the same.

This is not a draft, not a “coming soon.” From that date the new records are part of what your PAC (Proveedor Autorizado de Certificación, the SAT-authorized timbrado provider) validates against before it stamps your CFDI. If your invoice carries a pedimento value, that value now gets checked against the refreshed catalog.

The scope is narrow but real: this touches CFDIs for ventas de primera mano (first-hand sales) of imported goods that reference a pedimento. If you only invoice domestic services, you can exhale. If you build for clients who sell imported goods on first hand — or you run a multi-tenant facturación platform where some tenant does — this lands on you.

Two sources to anchor on: the SAT itself, and the practical write-up from LLB Solutions on the June 2026 CFDI 4.0 catalog update. Treat both as the starting point and confirm the current catalog version directly before you ship — these things move, which is the whole point of this post.

EffectiveJune 19, 2026 — already in force
WhatNew mandatory entries in c_NumPedimentoAduana
ScopeCFDIs for first-hand sales of imported goods with a pedimento
XSDNOT changed — no schema bump
Your actionRefresh catalog data; no code change

Why your integration code doesn’t change

This is the reassuring part, and it’s worth saying plainly: the XSD schemas were not modified. No new attributes, no new nodes, no version bump on the CFDI 4.0 schema. The technical structure of the XML you build is identical today to what it was on June 18.

That means your serialization stays the same. Your XML builders stay the same. Your validate-against-XSD step stays the same. The bindings you generated from the schema — whatever language you’re in — are still correct. There’s nothing to regenerate, nothing to redeploy on the code side.

What actually changed is the contents of an allowed-values list. c_NumPedimentoAduana is a catálogo: a dataset of permitted values that the SAT maintains and the XSD references by reference, not by enumeration. Adding records to that dataset doesn’t alter the schema’s shape — it alters which values pass validation against the current catalog.

So the only thing you need is the refreshed c_NumPedimentoAduana data loaded into your pipeline, in whatever form you validate against. If your pipeline already syncs catálogos automatically, this update may have already flowed through with zero human action — that’s the goal. If it doesn’t, this is your wake-up call, and the rest of this post is the fix.

This pipeline, by the way, is the one I built out in auto-invoicing CFDI from Stripe and Mercado Pago — catalog resilience is exactly the kind of thing that decides whether that automation survives contact with production.

Data refresh vs code change: knowing the difference

If you internalize one mental model from this post, make it this: SAT changes come in two flavors, and you triage every bulletin by asking which one you’re looking at.

XSD / schema changes touch your code. A new attribute, a new node, a new complemento (a structured add-on to the base CFDI), a version bump — these change the shape of the XML. They mean redeploying code, regenerating bindings from the new schema, and updating your validators. Real engineering work, real release.

Catálogo changes touch your data. New or updated allowed values in a list the SAT maintains. No shape change, no redeploy. You refresh a dataset and move on. The XML you build is byte-for-byte structurally the same; only which values are valid has shifted.

The June 2026 c_NumPedimentoAduana update is firmly the second kind. New mandatory entries in a catalog, XSD untouched. Read it as a code change and you’ll waste a sprint hunting for schema diffs that don’t exist. Ignore the data side because “nothing in the schema changed” and your PAC starts rejecting valid-looking invoices. The skill is telling the two apart fast — and then doing the small right thing instead of the large wrong one.

What changed (DATA)

  • New mandatory records in c_NumPedimentoAduana
  • An allowed-values catálogo, not the schema
  • Fix = refresh the dataset
  • No redeploy needed

What did NOT change (CODE)

  • XSD schemas untouched
  • No new attributes or nodes
  • No CFDI version bump
  • Your builders and validators stand

Why hardcoding catalogs breaks your pipeline

Here’s the concrete failure mode. Say you baked c_NumPedimentoAduana into your app as a constant — a checked-in list, an enum, a seeded table you never refresh. When the SAT adds new mandatory records, your in-code copy is now stale.

Two ways that bites you. First, a new pedimento value your client legitimately needs looks invalid to your own pre-checks, so you reject a perfectly good invoice before it ever reaches the PAC. Second — worse — the SAT now requires matching against records you don’t have, so you send something the PAC validates against the current catalog and bounces.

And that rejection shows up at the worst possible moment: at timbrado time, in production, on a real first-hand-sale invoice a client is waiting on. From your app’s perspective it can feel silent — a generic validation error code buried in a PAC response, not a friendly “your catalog is stale.” You debug XML structure for an hour before realizing the structure was never the problem; the data was.

The root cause is conceptual: a hardcoded catalog is a constant that the SAT mutates out from under you. You wrote const over something the regulator treats as a living dataset. The fix isn’t a one-off patch for this June update — patch it by hand and you’ll be back here next month. The fix is architectural.

Sync current catálogosScheduled job pulls latest from PAC/SAT → validate inputs → timbrado succeeds
OR: hardcoded stale catalogApp skips the refresh, trusts its baked-in list
PAC validates against CURRENTNew mandatory records aren't in your copy
Silent rejectionGeneric error at timbrado, in production, on a real invoice

SAT catálogos change constantly — this is the pattern

Don’t treat June 19 as a freak event. c_NumPedimentoAduana alone moved multiple times across 2026 — refreshed on February 13, March 13 and May 21 before this June update (confirm the current dates and version against your PAC). One catalog, several revisions in a single half-year, and that’s just one of dozens.

Look wider and the pattern is everywhere. Carta Porte 3.1 ships its own set of catálogos that update on their own cadence. The hydrocarbons complement leans on the L_CNE list, which updates daily. Different catalogs, different rhythms, same underlying truth: these are moving data, not fixed constants.

So the durable lesson isn’t “remember to update c_NumPedimentoAduana in June.” It’s: treat every catálogo as data you refresh on a schedule, never as a constant you hardcode. Build for the cadence, not the calendar.

One honest caveat, because I’m an engineer and not a contador (accountant): I’m telling you how to keep the pipeline correct. Whether a specific pedimento or first-hand-sale operation is fiscally right for your client is a compliance question — confirm the current SAT rules and defer the fiscal edge cases to a contador or compliance specialist. The engineering job is making sure the right data reaches the PAC; the fiscal job is deciding what the right data is.

How to make your CFDI pipeline catalog-resilient

Four moves turn the next catalog change from a fire drill into a non-event.

Step 1 — sync catálogos on a schedule. A job that pulls the latest catalog data from your PAC or the SAT automatically. Daily is a sane floor; for L_CNE you’ll want it that often anyway.

Step 2 — validate every input against the current catalog before you timbrar. Catch stale or unknown values in your own pipeline, with a clear error, instead of letting the PAC reject them opaquely later.

Step 3 — alert on a version bump. When the synced catalog’s version or hash changes, tell a human. Not to block — to know. That’s how you find out the SAT moved before a client does.

Step 4 — never hardcode catalog values. Read them from the synced dataset, always. No enums, no checked-in constants for anything the SAT maintains.

Here’s the shape of it, vendor-neutral and illustrative — adapt the fetch to your PAC’s actual API:

# Illustrative: scheduled catálogo sync + pre-timbrado validation.
# Vendor-neutral — wire fetch_catalog() to your PAC or SAT source.

import hashlib

CATALOG = "c_NumPedimentoAduana"

def sync_catalog(store, notifier):
    """Step 1 + 3: pull latest, store it, alert on a version bump."""
    raw = fetch_catalog(CATALOG)          # PAC/SAT call (provider-specific)
    new_version = hashlib.sha256(raw.bytes).hexdigest()[:12]
    current = store.get_version(CATALOG)

    if new_version != current:
        store.save(CATALOG, raw.values, new_version)  # never hardcode: it lives in the store
        notifier.alert(f"{CATALOG} bumped: {current} -> {new_version}")  # human finds out
    return new_version

def validate_before_timbrar(cfdi, store):
    """Step 2 + 4: check inputs against the CURRENT catalog, loaded from the store."""
    valid_values = store.get_values(CATALOG)   # current data, not a baked-in constant
    for pedimento in cfdi.pedimentos:
        if pedimento not in valid_values:
            raise CatalogError(
                f"{pedimento} not in current {CATALOG} "
                f"(v{store.get_version(CATALOG)}). Re-sync, then retry."
            )
    # structure is validated separately against the (unchanged) XSD
    return True

Notice what the code does not do: it never embeds catalog values, and it never trusts a stale copy at timbrado. The XSD validation is a separate, untouched step — because the schema didn’t change. That separation is the whole architecture.

  1. Sync on a scheduleJob pulls latest catálogos from your PAC or the SAT automatically
  2. Validate against currentCheck every input before timbrado, fail with a clear error
  3. Alert on version bumpNotify a human when a catalog version or hash changes
  4. Never hardcodeLoad catalog values from the synced dataset, always

FAQ

Do I need to redeploy my code for the June 2026 update? No. The XSD didn’t change, so your integration code stays as-is. Refresh your c_NumPedimentoAduana catalog data and you’re done.

Does this affect all my invoices? No. It mainly touches CFDIs for ventas de primera mano (first-hand sales) of imported goods that carry pedimento data. Purely domestic invoices without pedimentos aren’t touched by this specific update.

How do I get the new catalog? Sync it from your PAC or the SAT. Most PACs publish refreshed catálogos; pull from whichever source your pipeline already trusts, and confirm you’re on the current version.

What if I do nothing? Once the new records are mandatory, your PAC may reject affected timbrado requests — and that rejection lands in production, on real invoices, often as an opaque error.

Is this a fiscal or an engineering question? The pipeline mechanics are engineering — sync, validate, alert. Whether a given pedimento is fiscally correct for your client is a contador/compliance call. Confirm current SAT rules.

The takeaway

June 19, 2026: new mandatory c_NumPedimentoAduana entries, XSD untouched, your code unchanged. The fix for this update is small — refresh the catalog. But fixing this one update by hand is the wrong win.

The real win is a pipeline where the next catalog change — and there’s always a next one — is a non-event. Sync catálogos on a schedule, validate every input against the current version before you timbrar, alert when a version bumps, and never hardcode a value the SAT maintains. Build that once and you stop reading SAT bulletins with your stomach in a knot.

Treat catálogos as moving data, not constants. Confirm the current SAT rules — they’re a dataset you refresh, not a number you memorize. More fiscal and payments build guides live in integrations.