Blog/Deliver media to Google Cloud Storage with a verifiable handoff
OverviewAll posts
Tutorial

Deliver media to Google Cloud Storage with a verifiable handoff

Configure GCS media delivery, check writer and reader permissions, and record object generations before passing files to downstream processing.

VTornado API team
Covered in this article
Configure the delivery destination
Verify writer and reader access
Record the accepted object generation
7 min reading time
Published October 8, 2026
VProduct guides by Velys Software

Delivering media to Google Cloud Storage requires more than saving a bucket name. Configure the destination, verify a completed delivery with the identity that will consume it, and record which object version your application accepted. This gives your processing queue a stable reference even when a temporary download link expires.

The workflow below uses Tornado for media delivery and Google Cloud Storage for the handoff to your application. It focuses on permissions and object identity; it does not claim a live cloud test or recommend a universal bucket region.

Configure the intended destination

Create the bucket first. In the Tornado dashboard's default storage destination settings, the GCS configuration takes a bucket, project ID and service-account JSON credentials. Keep the key in the configuration flow, not in your workflow logs or source repository.

The dashboard and direct API use different contracts. The dashboard calls its credentials field credentials_json; the per-key POST /user/gcs endpoint uses service_account_json. Follow the GCS setup guide for your chosen route instead of moving payloads between them.

For the dashboard GCS path, a structurally accepted configuration is not a live credential check. Verify an actual delivery before treating setup as complete. Save the job ID and the destination you intended to use so that a later investigation can distinguish a configuration problem from a source-media problem.

Separate the writer from the reader

Consider the complete set of operations in the delivery path. A create-only permission can be insufficient when an upload also needs to compose parts, read an object or clean up temporary objects. Tornado's GCS guide describes those operations. Review the current path before choosing a bucket-scoped role.

Google's Cloud Storage IAM role reference distinguishes Object Creator, Object Viewer and Object User. Object Creator does not provide general read or delete access; Object User includes broader object operations. Choose the permissions your integration needs and apply them at the intended scope. A role on an unrelated project or bucket does not solve a failure on the destination object.

Your processing service has a separate access decision. It may read through its own Google identity or through a signed URL. Test the path it will actually use. A successful download from an administrator's laptop does not establish that a deployed worker can read the same object.

For signed access, the signing identity needs permission for the requested operation. Anyone holding the link can use its permitted access until it expires, subject to the underlying authorization conditions. Keep these links out of public logs and analytics. See Google's signed URL documentation.

Verify one delivery before queuing downstream work

After the media job reaches a successful outcome, identify the exact destination object. Check its metadata using an appropriately authorized Google identity. The following read-only command uses placeholders; substitute the bucket and object path from your delivery record:

gcloud storage objects describe 'gs://YOUR_BUCKET/PATH/TO/FILE.mp4' --format=json

The command reference documents this operation. Inspect the returned metadata rather than interpreting the absence of a console error as a complete verification.

Then exercise the consumer's actual read path and validate the media itself: can its parser open the file, does it contain the expected tracks, and is the duration consistent with the requested output? Metadata access and a nonzero byte count are useful checks, but neither proves that the file is suitable for transcription or editing.

Keep these observations separate:

ObservationWhat it establishes
Configuration savedA destination setting was accepted
Job completedThe media job reported success
Object metadata retrievedThat identity could inspect that object
Consumer read succeededThe intended reader could access the bytes
Media validation passedThe file met your application's tested requirements

Only the last steps support handing work to a consumer that depends on readable, suitable media.

Record the generation, not only the object name

Google Cloud Storage assigns an object generation to a data version. The metageneration tracks metadata changes and serves a different purpose. See Google's object metadata reference.

Store bucket, object name and generation with your job ID. If another upload later replaces the object at the same name, your record can still state which version was accepted. That record does not preserve the old bytes: retention, versioning and deletion settings determine whether the generation remains available.

The following example validates an application-owned receipt after you map observed metadata into it. It is not a parser for Tornado's status response or an assumption about the Google CLI's JSON field names:

def accepted_object(receipt, expected_bucket, expected_name):
    if receipt.get("bucket") != expected_bucket:
        raise ValueError("Unexpected bucket")
    if receipt.get("name") != expected_name:
        raise ValueError("Unexpected object")
    generation = receipt.get("generation")
    if (not isinstance(generation, str)
            or not generation.isascii()
            or not generation.isdigit()
            or int(generation) <= 0):
        raise ValueError("Missing or invalid generation")
    size = receipt.get("size_bytes")
    if type(size) is not int or size <= 0:
        raise ValueError("Expected nonempty media")
    return (expected_bucket, expected_name, generation)


receipt = {"bucket": "example-media", "name": "jobs/example/video.mp4",
           "generation": "123456789", "size_bytes": 1024}
assert accepted_object(receipt, "example-media", "jobs/example/video.mp4") == (
    "example-media", "jobs/example/video.mp4", "123456789"
)

The receipt and generation above are synthetic. The function and rejection cases were executed locally; no Google credentials, live bucket or media job were used. It validates fields, not authenticity, access or media quality. Persist only observations from a trusted lookup associated with the intended job.

When strict version identity matters, use a read operation that explicitly targets the recorded generation, and check the semantics of the client you use. Merely storing a generation while reading the latest object by name leaves a replacement race. Do not append parameters to an existing signed URL: changing its signed request can invalidate it.

Recover at the failed boundary

If the writer fails, investigate its destination and permissions. If the object exists but the reader receives a denial, inspect the reader or signing identity. If a temporary link expires, obtain appropriate fresh access rather than immediately processing the source again. The expired-link guide explains that distinction.

Track downstream acceptance and completion separately from media delivery. Use the recorded object identity to investigate repeat processing, and retain the job ID for status recovery. A storage receipt alone is not a guarantee that your queue executes work exactly once.

Start with the first media workflow, configure your GCS destination, and validate a representative file through the actual consumer. Before expanding the workload, estimate its cost from measured usage, including your storage and downstream processing separately.