← Back
Home › Systems › Energy, industry and public infrastructure

customs-single-window

declarations, tariffs, risk targeting and clearance: HS tariff engine, origin and valuation rules, risk selectivity lanes, multi-agency window

UNCTAD ASYCUDA, CBP ACE, EU ICS2, Singapore TradeNet

Open the customs-single-window pagecustoms-single-window Hdr histogramdata-structures: Log-linear bucketed histograms with bounded relative error for latency percentilesDeterministic simulationtestingdistributed: Run the whole system in one thread under a seeded scheduler with simulated network, disk and clock, so every failure replays from its seed.›Durable workflow executiondistributed: Ordinary code made crash-proof by logging every side-effect result and replaying the code from that log; activities run at least once, workflow logic must be deterministic.›Primary-backup replicationdistributed: One node orders all writes and ships them to standbys; the acknowledgement rule (none, one, all) sets what a crash loses, and an external arbiter decides who takes over.›Saga patterndistributed: Split a cross-service operation into local transactions, each paired with an undo; on failure run the undos in reverse, or retry forward.›Hot-standby redundancyembedded: A second, always-current controller shadows the primary and takes over within a few cycles of silence; epochs stop the old primary from fighting back.›Currency cross ratetriangulationfinance: Deriving cross rates through a base currency with correct precision, inversion and spreadsMoney representationfinance: Store money as integer minor units with an explicit currency exponent, compute in wider integers, and round once with a named rounding mode.›Payment state machinefinance: Persisted state machine for a payment lifecycle (authorize, capture, void, refund) that treats timeouts as unknown, retries with the same key and applies webhooks idempotently.›Reconciliationfinance: Match internal ledger records against bank and processor statements by reference, then amount and date window; every unmatched or mismatched item becomes an exception.›Tariff ratingfinance: Rate usage events against price plans: tiers, time bands, bundles and rounding rulesProcess supervisionhardware-os: A parent process starts services, reaps their exits and restarts them under a delay and burst-limit policy, then stops them with SIGTERM before SIGKILL.›Gradient-boosted decision trees(GBDT)machine-learning: Add small regression trees one at a time, each fitted to the loss gradient; histogram splits over 256 bins make it the strongest tabular default.›Text classificationmachine-learning: Assign one or more labels to a text; climb a ladder from regexes to linear models on n-grams, embeddings, fine-tuned encoders and LLM prompts, only as far as the data and budget demand.›X12 edi messagingnetworking: ANSI X12 and EDIFACT interchanges: segment/element delimiters, ISA/GS/ST envelopes, control numbers and 997/CONTRL acknowledgementsBill of materials explosionoptimization: MRP netting: explode BOMs level by level with lead-time offsetting into planned ordersAho-Corasick multi-patternmatchingsearch-sort: Find all occurrences of many fixed patterns in one pass over the text, in time independent of the pattern count, using a trie with failure links.›Fuzzy string matchingsearch-sort: Edit distance, Jaro-Winkler and trigram indexes for approximate name and address matchingCoverage-guided fuzzingsecurity: Mutate inputs, run an instrumented target, and keep any input that reaches a new code edge; feedback turns blind guessing into incremental search.›Mutual TLSsecurity: TLS where the client also proves a certificate key, so every connection carries a verified, rotatable machine identity.›Reputation scoringsecurity: Turn per-entity good/bad event counts into a trust score that forgets old evidence, borrows strength from parent groups for new keys, and maps to allow, challenge or block.›Row-level security(multi-tenant isolation in the…security: Append a policy predicate to every query inside the database, so a forgotten WHERE tenant_id = ? in application code cannot leak another tenant's rows.›Tamper-evident audit logsecurity: An append-only log whose Merkle root, signed per batch, lets anyone verify an entry is present and history was never rewritten, with log-size proofs.›Trusted timestampingsecurity: RFC 3161 time-stamp tokens: a TSA signs a hash with time, proving data existed thenBitemporal datastorage: Valid time plus transaction time on every record for as-of queries, back-dating and correctionsEntity resolutionstorage: Decide which records describe the same real-world thing: block to cut the pair space, score pairs with a probabilistic model, cluster the matches transitively.›Point in time joinstorage: As-of join that picks the latest feature value at or before each event timestampPoint-in-time recoverystorage: Periodic base backups plus a continuously archived change log let you rebuild the state as of any instant, then branch from there.›XML Streaming Parserstorage: Tokenize XML into start, text and end events in constant memory, so a few fields can be extracted from huge, broken or hostile documents.›Graceful drainingsystems: On a termination signal, stop taking new work, finish in-flight work until a deadline, tell clients to reconnect elsewhere, then exit; clients resume from a token.›Publish subscribe event bussystems: In-process or on-box topic bus decoupling producers and consumers with bounded queuesRete rules enginesystems: Match many rules against changing facts by caching partial matches in a shared network, so each change only does the work it actually affects.›Cooperative schedulingconcurrency: Green threads/coroutines yielding at await points on a user-space schedulerFault injectiondistributed: Inject latency, errors, kills and partitions via proxies, netem or hooksRecord replaydistributed: Record every nondeterministic input (time, randomness, I/O, model and tool outputs, per-tick player input) and re-execute deterministically for debugging, replays and state rebuildHierarchical timing wheelsystems: Timers bucketed by expiry into wheels of slots, coarser wheels for far deadlines: O(1) insert and cancel for millions of timeouts.›Durable job queuedistributed: Persisted tasks that workers lease for a visibility timeout, ack on success and retry with backoff; at-least-once execution that survives worker and queue crashes.›Leasesdistributed: A lock that expires on its own: bounded-time ownership that survives crashes and partitions, given bounded clock drift and fencing tokens on writes.›Health checkingsystems: Decide per backend whether to route to it using probes and real-traffic errors, with consecutive-count hysteresis, capped ejection and a panic fallback.›Transactional outboxdistributed: Write the event as a row in the same local transaction as the state change; a relay publishes it later at-least-once, so state and events never diverge.›Watchdog timerembedded: A hardware down-counter that resets the chip unless software proves, in time and in a plausible pattern, that it is still healthy.›Event sourcingfinance: Persist state changes as an append-only stream of immutable events per entity; current state is a fold over them, guarded by expected-version appends.›Idempotency keysfinance: Client-chosen unique key per logical operation, stored with a request fingerprint and the final response, so retries replay the first result instead of acting twice.›Exponential backoff with jittersystems: Retry after delays that grow geometrically and are randomized, so failed clients spread out instead of hammering a recovering service in synchronized waves.›+ 43 moreTransparent interceptionnetworking: Redirect traffic to a local proxy (iptables TPROXY/REDIRECT, eBPF) without app changesSyscall interceptionsecurity: Intercept syscalls via ptrace, seccomp user notify or a user-space kernelSlab allocatorsystems: Pre-carve pages into equal-size slots per size class with an in-place free list: O(1) alloc and free, no external fragmentation, cache-warm reuse.›Dead-letter queuedistributed: A side queue for messages that keep failing: after N delivery attempts or a permanent error, park them with the reason so the main queue keeps moving.›Fencing tokensdistributed: A number that grows with every lock grant and is checked by the protected resource, so a paused ex-holder's late write is rejected.›Event deduplication windowdistributed: Keep the ids of recently accepted events for a time window and drop repeats; memory stays bounded by rotating time buckets instead of per-entry timers.›Change data capturestorage: Decode the database's own commit log into row-change events in commit order, with a resume position, so any consumer can mirror the data exactly.›Optimistic concurrency controlstorage: Run transactions without locks, record what they read, and validate at commit that nothing changed; fastest when conflicts are rare.›Cryptographic hashes (SHA-2,BLAKE3)security: Collision-resistant fingerprints of data; SHA-256 for interoperability, BLAKE3 for speed, HMAC/HKDF for keyed uses.›+ 18 moreEbpfhardware-os: Verified in-kernel programs on hooks (kprobes, XDP, tc) with mapsPtrace process controlhardware-os: Ptrace: attach, read and write registers and memory, single-step, intercept signalsSeccomp bpfsecurity: Syscall filtering with seccomp BPF programsConsumer offsetsdistributed: Each consumer group stores one integer per partition, the next position to read, so a restarted consumer resumes where it left off with at-least-once delivery.›Incremental sync cursorsdistributed: The client keeps one opaque token; each pull returns only the changes after it plus the next token, so sync cost scales with what changed, not with the dataset.›Message sequence numbersdistributed: A per-stream counter on every message lets the receiver detect loss, duplicates and reordering, ack cheaply and resume after reconnect with "send from N".›+ 8 moreCursor paginationnetworking: Keyset/opaque-cursor pagination for stable incremental reads from APIs and tables

How it is built

Architecture overview

A customs single window is the system through which a trader declares a shipment once and the state does the rest: classify the goods, work out the duty and taxes, decide how closely to check the shipment, ask the other agencies (health, agriculture, standards, transport) for their permits, and release the goods. The WTO Trade Facilitation Agreement describes the goal: traders submit documents and data through a single entry point, and agencies do not ask again for what they already received (Article 10.4, see Sources). Real systems are ASYCUDA, CBP's ACE, Singapore's TradeNet and the EU's customs systems; most are proprietary or government-built.

Three ideas hold the design together. First, a declaration is a document that is processed in stages, not a request that returns an answer: it is received, validated, rated, risk-scored, routed, checked, possibly corrected, and finally cleared, and each stage can pause for days. Second, the rules are data with validity dates: tariffs, origin rules, bans and quotas change on known dates, and the system must answer "what applied to this goods code on the day of import". Third, risk, not rules, decides who is checked: a small share of declarations gets a document check or an inspection; the rest are released automatically, and the scoring that decides which is the lever customs administrations tune most.

Parts:

 trader / broker ──declaration (WCO-style message)──► intake: mTLS, schema check, idempotency
                                                              │
                                              master data (versioned) ◄── tariff & rules updates
                                                              ▼
                 classify ► rate duty ► value + origin ► permits requested from agencies
                                                              │
                                              risk engine: score ► lane (release | docs | inspect)
                                                              │
                  ┌───────────── workflow case (days, many actors) ──────────────┐
                  │ agency A permit  agency B permit  inspection  payment  guarantee │
                  └─────────────────────────────┬─────────────────────────────────┘
                                                ▼
                       release ► ledger posting ► audit log ► change feed ► statistics, post-clearance audit

How the parts fit together

Declaration intake

Declarations arrive from traders, brokers and carriers as structured messages. Many administrations model them on the WCO data model; HMRC's public Customs Declarations service, for example, validates each request against the WCO declaration schema before passing it on (see its repository). Intake does three things and nothing else: authenticate the sender (Mutual TLS for machine-to-machine links, Authorization models for what a broker may declare for which company), check the message against the schema, and store it with an idempotency key so a retry after a timeout does not create a second declaration. Large messages are read with an XML streaming parser in constant memory; legacy partners still send EDIFACT-style interchanges (x12-edi-messaging, no page yet). The response is only an acknowledgement; results arrive later as notifications, as the HMRC description also says.

Master data

Traders, importers, consignors, carriers and goods are referenced by id. The same company appears under many spellings and registrations, so Entity resolution (blocking, scoring, clustering; record-linkage, no page yet) links party records for risk history. The key property is time: a tariff measure has a validity range, a company's authorisation has a validity range, a ban has a validity range, and corrections can be retroactive. bitemporal-data (no page yet) stores both the real-world validity and the time the system learned it, so the system can answer "what did we believe on the day we cleared this?". Range lookups ("which measures cover this code on this date") use an Interval tree or sorted range arrays; old months are cut with Time partitioning; MVCC lets a declaration be rated against a stable snapshot while a rule update is loaded.

fuzzy-string-matching (no page yet) is the candidate step in front of entity resolution: it finds party and address records whose names differ by a typo, an abbreviation or a transliteration, so blocking does not miss a trader who declared under a variant spelling. point-in-time-join (no page yet) is how declarations meet the versioned reference data: each declaration is joined to the tariff measure, authorisation or ban that was valid on its own relevant date, not the one valid today, which is what makes a retroactive correction or a late declaration come out right.

Classification and tariff

Every line of a declaration carries a goods code. The international Harmonized System gives six digits that are used worldwide; each administration adds its own further digits, and decisions on specific products are national (GOV.UK). The tariff is a tree over those digits, and measures (duty rate, quota, licence, prohibition, certificate) attach at any depth and are inherited downward, filtered by origin country, date and trade agreement. The UK Trade Tariff backend is the clearest open example of this data model: it imports tariff updates and serves commodity codes, duties, measures, quotas, certificates and rules of origin (see trade-tariff/trade-tariff-backend).

Goods descriptions in the declaration are free text, so classification support is a Text classification problem (regex and keyword rules first, linear models or fine-tuned encoders next) with Aho-Corasick for fast keyword and prohibited-term matching. The classifier advises: it suggests a code or flags a mismatch between description and code, and the case goes to an officer when confidence is low. A binding decision comes from an advance ruling, which the WTO Agreement makes binding on the issuing authority for the applicant (Article 3). The duty calculation itself is Money representation work: integer minor units, a fixed rounding rule per line or per declaration, and exact allocation of lump-sum charges across lines (largest-remainder-rounding, no page yet). Rate-by-tier and per-unit rules are the tariff-rating (no page yet) pattern.

Valuation and origin

The customs value starts from the transaction value, the price actually paid, plus defined adjustments; if that cannot be used, the WTO Customs Valuation Agreement gives a fixed sequence of fall-backs: identical goods, similar goods, deductive value, computed value, and last a flexible method (see the agreement). That sequence is a decision list, easy to encode in a Rete rules engine where each rule states its conditions and a priority; exchange rates come from the administration's published rate (currency-cross-rate-triangulation, no page yet).

Origin decides whether a preferential rate applies. The WTO Agreement on Rules of Origin names the usual tests: goods wholly obtained in a country, or substantially transformed there, with "change in tariff classification" as one criterion (text). Proof is a bill-of-materials problem: a product's origin depends on the origin and classification of its inputs, so checking a claim means exploding the BOM (bill-of-materials-explosion, no page yet) in dependency order (Topological sort) and testing each rule. Most administrations only verify these claims after clearance, from the exporter's records.

Risk management

Article 7.4 of the Trade Facilitation Agreement asks members to adopt a risk management system, concentrate control on high-risk consignments, and expedite low-risk ones; selectivity criteria may include the HS code, description, country of origin, value, and the trader's compliance record. The engine has three layers:

  • Rules from intelligence (a code and origin combination under suspicion, a sanctioned party), run in a Rete rules engine so that thousands of rules cost little per declaration.
  • A learned score from Gradient-boosted trees trained on past inspection results (did the check find a fault?). Calibration makes the score a probability, so the inspection threshold can be set from capacity: "inspect the top few percent".
  • Trader trust: a running record of how often a party's declarations were found compliant, kept with Reputation scoring and Bayesian rate estimation so that a trader with few declarations is not trusted too early, and Sliding-window counters for recent volumes. The Agreement's authorized-operator measures (Article 7.7) are the formal side of this: operators with a record of compliance get faster release.

The result is a lane: automatic release, document check, or physical inspection, with a random selection that keeps the model honest (Article 7.4 allows random selection too). Without random checks the model never learns about the low-scored fraud it misses; training data would be only what was already inspected.

Workflow and multi-agency window

The single window is mostly workflow. One declaration can wait for a veterinary certificate, a standards approval, an inspection slot, a payment and a guarantee, each owned by a different agency with its own system. Durable workflow execution fits: the case logic is ordinary code whose steps are logged, so it resumes after a crash while waiting for days. Calls to agencies are asynchronous: requests go out through a Transactional outbox so that "state changed" and "message to agency queued" commit together, and delivery uses a Durable job queue with retries. A cancelled declaration or a refused permit must undo earlier steps (release a guarantee, withdraw an agency request), which is the Saga pattern. Case states (received, held, inspection ordered, released, amended) form a Hierarchical state machine; deadlines (permit validity, storage time, appeal windows) are timers in a Hierarchical timing wheel.

The Agreement also asks for release before the final determination of duties if a guarantee is given (Article 7.3). In the data model this means a declaration has two lifecycles: physical release and financial finalisation, which can be weeks apart.

Agencies need to learn that a case changed without the window calling each of them in turn. publish-subscribe-event-bus (no page yet) carries the case events (submitted, held, inspected, released) on topics, and each agency subscribes to the topics its permits cover. The workflow publishes through the outbox, subscribers receive at least once and must tolerate repeats, and an agency's reply returns to the case as an event, so the case logic stays the single owner of the declaration's state.

Accounts and payment

Duties are money owed to the state. A Double-entry ledger records the liability at release, payments, guarantees drawn, and refunds as postings that sum to zero; the Payment state machine handles bank and card payments that time out into an unknown state; Reconciliation matches bank statements to the open items each day. Corrections after release (amendments, audit findings) are new postings, never edits.

Records, audit and reporting

Customs decisions are legally contestable, so the record must show who changed what and when. A Tamper-evident audit log holds decisions, officer actions and rule versions used. Post-clearance audit selects traders on risk and feeds its findings back into the score (Article 7.5.4). Change data capture streams committed changes to the statistics system and to partner agencies without double-writing, and Point-in-time recovery plus Row-level security (one trader sees only its own declarations) complete the data tier.

trusted-timestamping (no page yet) anchors the audit log's signed head with a time from an independent authority, so a decision's date can be proven in a dispute without trusting the customs system's own clock. It is applied to the log's periodic roots rather than to every entry, and the receipt is stored with the root.

Availability and assurance

Traders file against cut-off times, so the window cannot be down while a node is replaced. Primary-backup replication keeps a second copy of the declaration and case store, and Hot-standby redundancy keeps the intake and case services warm on a second node so a takeover is a switch, not a restore; Point-in-time recovery stays the answer for a bad write that replication would copy. Process supervision restarts a failed service, and Graceful draining lets it finish open submissions before a release. hdr-histogram (no page yet) records submission-to-lane latency, so the 99th and 99.9th percentiles can be watched in the hours before a filing deadline. Deterministic simulation testing replays failover, duplicate submissions and agency timeouts with injected faults and checks that no declaration is lost or released twice. Coverage-guided fuzzing mutates submitted XML and EDI messages, the input that arrives from outside parties.

Data and control flow

One import declaration

  1. The broker submits the message; intake authenticates, validates the schema, stores it under an idempotency key and acknowledges.
  2. The system resolves parties and loads the tariff as of the declaration date from the versioned rule store.
  3. For each line: check the goods code against the description, apply the measures (duty, quota, licence), compute the value and origin outcome, and compute duty in integer money.
  4. The risk engine scores the declaration and picks a lane.
  5. Required agency permits are requested; the workflow waits for each answer (minutes to days).
  6. If a check is ordered, an officer gets the case and records the result; findings update the trader record.
  7. On payment or a guarantee, the declaration is released; the ledger posts the liability; the audit log and change feed receive the events.
  8. Later: post-clearance audit samples declarations by risk and may issue an amendment.

Most time is spent waiting for people and agencies, not in computing: the calculation steps are milliseconds to seconds, while a held case takes hours or days. For this reason release-time measurement matters; the Agreement encourages members to measure and publish average release times (Article 7.6).

One tariff update

  1. A legal change arrives with a validity date.
  2. It is loaded as new versions with the valid-from date; nothing is overwritten.
  3. A diff against the current snapshot is reviewed, then published.
  4. Declarations lodged before the date continue to rate under the old rule, as their lodged date picks the version.

Scaling

  1. Case count is bounded by the workflow engine, not by CPU. Levers: keep case state small, partition by declaration id, archive finished cases to cheap storage.
  2. Spikes come around deadlines, month ends and port arrivals. Levers: accept and queue at intake (the acknowledgement is cheap), process asynchronously, drain with a bounded worker pool.
  3. Rules and tariff lookups become the hot path. Levers: cache the tariff snapshot per day in memory, precompute inherited measures per code, index measures by (code, origin, date).
  4. Risk scoring stays cheap if features are precomputed: trader history and code statistics are updated incrementally.
  5. Agency integration is limited by the slowest agency. Levers: timeouts with reminders, a dead-letter lane for stuck requests, a published service level.
  6. Availability matters near ports: a stopped system means trucks queue. Levers: replicas, a documented manual fallback procedure, and offline acceptance.

Failure modes

FailureSymptomCauseFix
Wrong tariff versionduty differs from the legal amountrule lookup by processing date instead of the legal datekey rules by declaration or acceptance date, bitemporal store
Duplicate declarationgoods declared twice, double liabilityretry after timeout without idempotencyidempotency key at intake, duplicate check on trader plus reference
Misclassificationdisputed duties, audit findingsfree-text description, similar codesclassifier plus officer review, advance rulings, post-clearance audit
Risk model blind spotfraud pattern never inspectedtraining only on inspected casesrandom selection, periodic retraining on audit results
Over-selectionlong queues, slow releasethreshold too low, stale rulesset threshold from inspection capacity, retire rules that never hit
Stuck casegoods wait with no owneragency answer never arrives, lost messagetimers and reminders, dead-letter lane, outbox with retries
Rounding differencestotals do not match bank paymentper-line versus per-declaration rounding, floatsinteger minor units, one named rounding rule
Retroactive correction breaks reportspast statistics changeupdate in placeappend corrections with valid time, as-of reports
Trader sees another's dataleak between companiesfilter in application code onlyrow-level security by trader id

Real implementations

namewhat it isrepolicencenotes
ASYCUDA WorldUNCTAD customs management system used by many countries——proprietary programme of UNCTAD; see asycuda.org
CBP ACEUnited States single window for import and export data——government system; see cbp.gov/trade/automated
TradeNetSingapore's trade declaration single window——see tradenet.gov.sg
UK Trade Tariff backendopen tariff data model and API (codes, duties, measures, quotas, rules of origin)trade-tariff/trade-tariff-backendMITthe tariff and measures part
HMRC Customs DeclarationsAPI that validates declarations against the WCO schema and forwards them to the customs systemhmrc/customs-declarationsApache-2.0the intake part; the backend system itself is closed

Sources

Similar systems

Share the most building blocks with this one.