If you sell digital products, a ZIP file is your shop window and your delivery van in one. A customer unpacks it and judges the whole product by what is inside. The mistakes that hurt are rarely dramatic: a file that opens on one computer and not on another, a PDF with the wrong number of pages, a leftover draft, a licence file that is missing, a ZIP that is no longer the one you tested. They are cheap to prevent and expensive to find after the sale. This guide explains a simple discipline: write down what you promise, check it by machine, and build the archive so that it comes out the same every time.
Write down your promises
A product listing makes claims: 12 posters in two page sizes, 24 PDFs, 12 images at a stated size, a 4-page guide, a licence. Each claim is checkable. The first step is to write them in one place, in a plain data file, before you build anything. A file such as release.json might list the expected file counts by type, page counts for key PDFs, image dimensions, size limits and the files that must exist.
Why a file and not memory? Because the listing page, the cover image and the archive drift apart over time. A written promise gives you a single reference that each check compares against. If you change the product, you change the promise on purpose, and the checks tell you what else needs to change.
What to check
Think of a release check as a short list of boring questions, each answerable by a program.
- File names. No characters that break on another operating system, such as colons, trailing spaces or names that differ only in capitals. No names that collide on a case-insensitive disk.
- Counts. The number of files of each type equals the promise.
- PDFs. Each has the page count you advertise.
- Images. Dimensions match, which is how you find a cover that was exported at the wrong size.
- Audio and video. Duration, sample rate or resolution within your limits.
- Licence. A licence file exists and says what you intend.
- Leftovers. No drafts, temp files, system files or notes to self.
- Wording. A scan for phrases you do not want to appear, such as placeholders.
- Archive. The ZIP contains exactly the manifest's files, and nothing else.
None of these needs judgement. That is why a program should do them, and why you should run them on every build, not only before a big launch.
The manifest and the checksum list
A manifest is a list of every file in the release with its path, size and a checksum. A checksum is a fixed-length fingerprint computed from the bytes of a file. NIST's Secure Hash Standard, FIPS 180-4, specifies hash algorithms, SHA-256 among them, and says the digests are used to detect whether messages have been changed since the digests were generated. A SHA-256 digest is 256 bits, written as 64 hexadecimal characters.
With a manifest you can answer questions that otherwise cost hours. Is this the same release I tested? Which file changed between version 1.0 and 1.1? Did a customer's copy get damaged on the way? Recompute the checksums, compare them, and you know.
Be clear about what a checksum does not do. It shows that bytes match. It does not show that the content is good, safe or authentic. A checksum published next to a file only protects against accidental damage, unless the checksum travels by a separate, trusted route.
Why two ZIPs of the same files differ
Zip a folder twice and compare the two archives. Often they differ, even though the files inside are identical. The reasons are mundane.
- Timestamps. Each member of a ZIP stores its own modification time. Python's zipfile documentation describes a date_time field for each member, a tuple whose year must be 1980 or later. Rebuilding changes the time, and so the bytes.
- Order. Members are stored in the order they were added. A different directory listing order gives a different archive.
- Attributes. The documentation lists an external_attr field for each member, which holds file attributes such as permissions. Different machines write different values.
- Compression. A different compression library or level yields different bytes, even for the same input.
- Extra fields. Some tools add extra data, such as extended timestamps or user IDs.
None of this affects what the customer sees, but all of it affects the hash. And a hash that changes for no reason makes it impossible to tell a real change from noise.
Making the build reproducible
The Reproducible Builds project gives a clear definition: a build is reproducible if, given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts. It adds that reproducibility is verified by bit-by-bit comparison, usually using cryptographically secure hash functions.
For a ZIP, the practical recipe is short.
- Sort the file list by path, so the order is always the same.
- Fix the timestamps to a constant value, such as the earliest date the format supports, instead of using each file's modification time.
- Fix the attributes to the same values for every file.
- Fix the compression method and level, and note the version of the tool in your build notes.
- Write nothing else in: no extra fields, no comments that change.
In Python this is straightforward, because you can create each ZipInfo yourself with a chosen date_time and external_attr, and add the members in sorted order. Then build twice and compare the SHA-256 of the two results. If they match, your build is repeatable. If they do not, find the difference before you ship.
One caution: reproducibility holds for the same environment. If you upgrade the compression library, the bytes may change, and then you need to record the new baseline. That is not a failure. It is the reason to write the environment down.
Worked example: a release check
This release is invented. The listing promises 12 PDFs in A4, 12 PDFs in US Letter, 12 PNG images, 12 SVG files and a few documents: a guide, a licence and a readme. The promise says 12 + 12 + 12 + 12 + 3 = 51 files. Suppose the folder holds 50. A count check reports that the SVG count is 11, and a look at the list shows that one file was saved under a different name. A hundred customers would have found it before you did.
Now the archive. You build the ZIP twice. Both come out at 18,432,000 bytes, which looks reassuring. But the size is no proof of identity. The two files could differ in many bytes and still have the same total, because timestamps, for instance, always occupy the same number of bytes. Compare the SHA-256 digests, which would differ in the case where one build used the current time and the other did not. That is why the comparison is by hash and not by size.
After fixing the timestamps and the order, you build twice more. The two digests match. You write the digest into the manifest. From now on, anyone can check the ZIP against that line.
A short routine
- Update the promise file if the product changed.
- Run the checks. Read every failure.
- Build the archive in sorted, fixed form.
- Verify the finished ZIP against the manifest: same files, same checksums.
- Open the ZIP as a customer would, on a clean folder.
- Keep the manifest and the checks' report with the release.
Tool that fits
The Digital Product Release Builder (EUR 24, paid once) is 15 small command-line Python tools for checking and packaging a product folder: manifest, filename check, deterministic ZIP, checksums, licence check, preview page, file counts, PDF page counts, image sizes, audio and video metadata, manifest validation, ZIP verification, wording scan and a release report. One release.json holds your promises, and release_report.py checks every one and writes Markdown and JSON reports. The ZIP files come out byte-identical on every run. It runs offline on the Python 3.10 standard library, with pypdf and ffprobe as optional extras, and includes a sample product, JSON Schemas, a manual, a release checklist and 71 tests. It is a quality aid and does not guarantee that a product meets any marketplace's rules. Use it in your own and your clients' projects; the kit may not be resold or shared.
What to do next
The Digital Product Release Builder has 15 offline Python tools that check a product folder against a release.json of your promises, then build a byte-identical ZIP with a manifest and checksums, with 71 tests. €24 once; it does not guarantee marketplace approval.
Questions people ask
Why can two ZIP files of the same content have different hashes?
A ZIP stores more than file contents. Each member carries its own modification time and attribute fields, members are stored in an order, and the compressor and its settings can differ. If any of these change, the bytes change, and so does the hash. Python's zipfile documentation lists a per-member date_time field and an external_attr field, for example.
What makes a build reproducible?
The Reproducible Builds project defines it this way: given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of the specified artifacts. For a ZIP, that means fixing the inputs that vary, such as timestamps and order, and writing down the rest.
Does a SHA-256 checksum prove a file is authentic?
No. It shows that the bytes match the ones that produced the checksum. NIST's Secure Hash Standard describes digests as a way to detect whether messages have been changed. If someone can replace both the file and the checksum, the check proves nothing about who made it.
Sources
- Reproducible Builds, Definitions: when is a build reproducible?
- Python documentation, zipfile: Work with ZIP archives (ZipInfo date_time and external_attr fields)
- NIST, FIPS 180-4: Secure Hash Standard (SHS)
- HiddenWay worked example: our own arithmetic, with an invented release
We checked each source on 17 October 2026. If a fact here is out of date, tell us at hello@hiddenway.info.