> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://uploadcare.com/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://uploadcare.com/docs/_mcp/server.

# Google Cloud Storage

> Use Uploadcare for file ingestion and processing, then transfer files to your own Google Cloud Storage bucket via a webhook-driven backend flow.

This guide explains how to use Uploadcare as an upload and processing layer while storing files in your own [Google Cloud Storage](https://cloud.google.com/storage) (GCS) bucket. This is not a native integration: Uploadcare does not write to GCS directly. Instead, your backend listens for webhook events, downloads the file from Uploadcare, and writes it to GCS using a service account.

This pattern suits teams that:

* Run workloads on Google Cloud and want to keep files in a bucket within the same project or region.
* Require fine-grained access control through IAM roles and service accounts.
* Want to apply Uploadcare processing (resize, format conversion, etc.) before committing files to long-term storage.

> **Warning**
>
> **Warning:** Once a file is removed from Uploadcare, CDN delivery and URL API transformations on the original file URL are no longer available. There is currently no built-in way to resume them without re-uploading the file. After the transfer, serve files directly from your GCS bucket or through Cloud CDN or another CDN layer in front of it.

## Prerequisites

Before you start, make sure you have:

* An active [Uploadcare account](https://app.uploadcare.com/) with a project and its public and secret API keys.
* A [Google Cloud project](https://console.cloud.google.com/) with the Cloud Storage API enabled.
* A GCS bucket created in the region closest to your users or backend. The bucket location is set at creation time and cannot be changed.
* A service account with the `roles/storage.objectUser` IAM role granted on the bucket. This role allows the account to read, create, and overwrite objects without granting broader project-level permissions.
* A JSON key file for that service account, downloaded from **IAM & Admin → Service Accounts → Keys** in the Google Cloud Console.
* A publicly reachable backend server capable of receiving HTTPS `POST` requests from Uploadcare.

## How it works

The flow is driven by Uploadcare webhooks:

1. A file is uploaded via the [File Uploader](/docs/file-uploader/) or [Upload API](/docs/uploading-files/).
2. Uploadcare processes the file and emits a `file.uploaded` webhook event to your endpoint.
3. Your backend receives the event, downloads the file from Uploadcare using the URL in the payload, and writes it to GCS using the service account credentials.
4. The file is deleted from Uploadcare or left to expire automatically.

Once the transfer is complete, your GCS bucket is the authoritative location for the file.

## Recommended flow

### Step 1: Upload a file

Use the [File Uploader](/docs/file-uploader/) for browser-based uploads or the [Upload API](/docs/uploading-files/) for server-side or programmatic uploads. At this stage, Uploadcare handles ingestion, validation, and any transformations you have configured.

To avoid holding files in Uploadcare longer than necessary, disable autostore for the upload. Non-stored files expire automatically after a retention period, which gives your backend sufficient time to complete the transfer before the file disappears.

To disable autostore on a per-upload basis, pass `UPLOADCARE_STORE=0` when using the Upload API, or set `store: false` in the File Uploader configuration. To disable it project-wide, go to **Project settings → Storage** in the [Dashboard](https://app.uploadcare.com/).

### Step 2: Receive the webhook event

Configure a webhook in your [Uploadcare Dashboard](https://app.uploadcare.com/) under **Project settings → Webhooks**. Set the event type to `file.uploaded` and provide the URL of your backend endpoint.

When a file is uploaded, Uploadcare sends a `POST` request with a JSON payload:

```json
{
  "id": 24877,
  "file": {
    "uuid": "eb7830fa-c59d-4ebb-ba2f-29b7c452ce84",
    "original_file_url": "https://ucarecdn.com/eb7830fa-c59d-4ebb-ba2f-29b7c452ce84/DSCN4715.JPG",
    "is_stored": false,
    "mime_type": "image/jpeg",
    "filename": "DSCN4715.JPG",
    "size": 7096467
  }
}
```

> **Warning**
>
> **Warning:** The host in `original_file_url` depends on your project's CDN configuration. It may be `ucarecdn.com`, a `*.ucarecd.net` subdomain, or a custom CDN domain. Use the URL as-is. Do not hardcode the host in your backend logic.

Your endpoint should:

1. Validate the request using the webhook signature (see below).
2. Extract `file.uuid`, `file.original_file_url`, `file.filename`, and `file.mime_type` from the payload.
3. Proceed to transfer the file to GCS.

> **Warning**
>
> **Warning:** Uploadcare signs webhook requests using a secret you configure in the Dashboard. In production, verify the `X-Uc-Signature` header before processing any payload. See [Webhooks](/docs/webhooks/) for signature verification details.

### Step 3: Transfer the file to GCS

Google Cloud Storage provides an official client library for interacting with GCS buckets. Examples use Node.js. Refer to the [Google Cloud Storage client libraries](https://cloud.google.com/storage/docs/reference/libraries) for other languages.

```bash
npm install @google-cloud/storage
```

Initialize the client using your service account credentials. Rather than referencing a key file path — which introduces a filesystem dependency and complicates containerized deployments — parse the JSON key content from an environment variable at runtime:

```javascript
import { Storage } from '@google-cloud/storage'

const storage = new Storage({
  credentials: JSON.parse(process.env.GCS_SERVICE_ACCOUNT_JSON),
})
```

If you prefer to reference the key file directly (for example, in local development), use `keyFilename` instead:

```javascript
const storage = new Storage({
  keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS,
})
```

Implement the transfer function. The example below fetches the file from Uploadcare and writes it to GCS in a single call:

```javascript
async function transferToGCS(uuid, fileUrl, filename, mimeType) {
  const response = await fetch(fileUrl)

  if (!response.ok) {
    throw new Error(`Failed to fetch file from Uploadcare: ${response.status}`)
  }

  const buffer = Buffer.from(await response.arrayBuffer())

  // Prefix with the UUID to prevent naming collisions between files that share
  // a filename and to keep object paths reconstructable from Uploadcare metadata.
  const objectName = `uploads/${uuid}/${filename}`

  await storage.bucket(process.env.GCS_BUCKET_NAME).file(objectName).save(buffer, {
    contentType: mimeType,
  })

  return objectName
}
```

Wire it into a minimal Express.js webhook handler:

```javascript
import express from 'express'

const app = express()
app.use(express.json())

app.post('/webhooks/uploadcare', async (req, res) => {
  const { file } = req.body

  if (!file) {
    return res.status(400).json({ error: 'Missing file payload' })
  }

  const { uuid, original_file_url, filename, mime_type } = file

  try {
    const objectName = await transferToGCS(uuid, original_file_url, filename, mime_type)
    console.log(`Transferred ${uuid} to GCS as: ${objectName}`)
    // Respond 200 so Uploadcare does not retry this event.
    res.status(200).json({ ok: true })
  } catch (err) {
    console.error(`Transfer failed for ${uuid}:`, err)
    // A non-2xx response causes Uploadcare to retry the webhook automatically.
    res.status(500).json({ error: 'Transfer failed' })
  }
})

app.listen(3000)
```

Returning a non-2xx status signals a failure to Uploadcare, which will retry delivery. This means transient errors — a momentary GCS outage, a DNS hiccup, or a timeout on a large file — are retried without any additional queue infrastructure on your end.

**Handing off a transformed version**

The webhook payload only carries `original_file_url`, the untouched upload. If your GCS bucket should receive a processed version instead, for example a resized thumbnail, build the CDN URL from the UUID and fetch that instead of the original.

Update the destructuring and the transfer call inside the same handler from earlier in this step, don't add a second app.post or app.listen. This only applies to images: guard on `mime_type` before building a transformed URL.

```javascript
const { uuid, original_file_url, filename, mime_type } = file

const isImage = mime_type.startsWith('image/')
const sourceUrl = isImage
  ? `${new URL(original_file_url).origin}/${uuid}/-/resize/1200x/`
  : original_file_url

const objectName = await transferToGCS(uuid, sourceUrl, filename, mime_type)
```

Operations can be stacked, for example `-/resize/1200x/-/format/webp/`. If format is changed, the `contentType` field passed to `save()` must be set explicitly to the new MIME type. Reusing `mime_type` from the payload would misreport the file's actual type once the format changes.

### Step 4: Handle file lifecycle

After a successful transfer, decide what to do with the original file in Uploadcare.

**Option A — Delete immediately via REST API**

Removing the file as soon as the transfer succeeds limits the window during which it is accessible from Uploadcare's CDN and keeps your Uploadcare storage usage predictable:

```javascript
async function deleteFromUploadcare(uuid) {
  const response = await fetch(`https://api.uploadcare.com/files/${uuid}/`, {
    method: 'DELETE',
    headers: {
      Authorization: `Uploadcare.Simple ${process.env.UPLOADCARE_PUBLIC_KEY}:${process.env.UPLOADCARE_SECRET_KEY}`,
      Accept: 'application/vnd.uploadcare-v0.7+json',
    },
  })

  if (!response.ok) {
    throw new Error(`Failed to delete file ${uuid}: ${response.status}`)
  }
}
```

Call `deleteFromUploadcare(uuid)` after a successful `transferToGCS` call in your webhook handler.

**Option B — Rely on auto-expiration**

If `is_stored` is `false` in the webhook payload, the file was uploaded without autostore and Uploadcare removes it automatically after a retention period. No explicit deletion call is needed provided your backend completes the transfer within that window.

Choose Option A when you need precise control over file retention and Uploadcare storage costs. Choose Option B when reducing backend complexity is the priority and the expiration window fits your use case.

## Important considerations

**Autostore behavior**

Uploadcare projects have autostore enabled by default, meaning uploaded files are retained indefinitely unless deleted. To use auto-expiration (Option B), disable autostore at the project level or pass `UPLOADCARE_STORE=0` via the Upload API per request. The setting is available under **Project settings → Storage** in the Dashboard.

**Idempotency**

Uploadcare retries webhook delivery when your endpoint returns a non-2xx response, so your handler may receive the same event more than once for the same file. The `save()` call above is safe to repeat: GCS silently overwrites the existing object at the same path. If you write associated records to a database, guard those writes with a check on the UUID to prevent duplicate entries.

**GCS object naming**

The example uses `uploads/{uuid}/{filename}` as the object name. The UUID prefix makes each path unique within the bucket regardless of what users name their files, and it lets you locate the GCS object later using only the Uploadcare UUID. You can adjust the prefix structure to suit your access patterns. For example, organizing by date (`uploads/2024/01/{uuid}/{filename}`) simplifies bucket-level lifecycle policies and makes cost breakdowns by period straightforward.

**Feature availability after file deletion**

Uploadcare's CDN delivery, URL API image transformations, and adaptive bitrate streaming all require the file to be present in Uploadcare. Once the file is deleted or expires, URLs served via Uploadcare's CDN for that file will return errors. Decide on your delivery strategy — [GCS uniform public access](https://cloud.google.com/storage/docs/access-control/making-data-public), [Cloud CDN](https://cloud.google.com/cdn/docs/setting-up-cdn-with-bucket), or [signed URLs](https://cloud.google.com/storage/docs/access-control/signed-urls) — before removing files from Uploadcare.

## Summary

This flow lets you use Uploadcare as an upload and processing layer while keeping files in Google Cloud Storage. Uploadcare handles ingestion and emits a webhook; your backend authenticates using a service account, downloads the file, writes it to GCS, and manages the lifecycle from there. CDN delivery and URL API transformations are only available while the file remains in Uploadcare, so finalize your delivery strategy before deleting or expiring files on the Uploadcare side.

For teams using Cloudflare R2, see [Cloudflare R2 storage](/docs/cloudflare-r2/). For Amazon S3, see [Direct uploads to S3](/docs/aws-s3-storage/) and [Copy files to S3](/docs/s3-integration/).