> 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.

# Azure Blob Storage

> Use Uploadcare for file ingestion and processing, then transfer files to your own Azure Blob Storage container 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 [Azure Blob Storage](https://learn.microsoft.com/en-us/azure/storage/blobs/) container. This is not a native integration: Uploadcare does not write to Azure directly. Instead, your backend listens for webhook events, downloads the file from Uploadcare, and uploads it to Azure Blob Storage using your Storage Account credentials.

Azure Blob Storage organizes files across three levels: a **Storage Account** holds one or more **containers**, and each container holds **blobs**. This guide uses "container" and "blob" consistently to match Azure's terminology.

This pattern suits teams that:

* Run workloads on Azure and want to keep files within the same subscription and region as the rest of their infrastructure.
* Need to comply with data residency policies that require files to stay within specific Azure regions.
* Want to apply Uploadcare processing (resize, format conversion, etc.) before writing 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 blobs directly from your container, through [Azure CDN](https://learn.microsoft.com/en-us/azure/cdn/), or via [Azure Front Door](https://learn.microsoft.com/en-us/azure/frontdoor/).

## 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.
* An [Azure subscription](https://portal.azure.com/) with a Storage Account created in the region closest to your users or backend. The region is fixed at Storage Account creation and cannot be changed.
* A container inside that Storage Account, created in the Azure Portal.
* A connection string for the Storage Account, available in the Azure Portal under your Storage Account settings. For production workloads, see the note on `DefaultAzureCredential` in Step 3.
* 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 your Azure Blob Storage container.
4. The file is deleted from Uploadcare or left to expire automatically.

Once the transfer is complete, your Azure container 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 keep files in Uploadcare only as long as necessary, disable autostore for the upload. Non-stored files expire automatically after a retention period, giving your backend time to complete the transfer before the file is removed.

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 Azure Blob Storage.

> **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 Azure Blob Storage

Azure Blob Storage provides an official client library for interacting with containers and blobs. Examples use Node.js. Refer to the [Azure Blob Storage SDK documentation](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-nodejs) for other languages.

```bash
npm install @azure/storage-blob
```

Initialize the client using your Storage Account connection string. The connection string encodes both the account name and key, so treat it as a secret. Store it in an environment variable and never commit it to source control:

```javascript
import { BlobServiceClient } from '@azure/storage-blob'

const blobServiceClient = BlobServiceClient.fromConnectionString(
  process.env.AZURE_STORAGE_CONNECTION_STRING
)
```

For production deployments running inside Azure (App Service, Azure Functions, AKS), use `DefaultAzureCredential` from `@azure/identity` instead. It authenticates via managed identity or service principal without any secret in your environment, which eliminates credential rotation overhead and reduces the risk of accidental exposure:

```bash
npm install @azure/storage-blob @azure/identity
```

```javascript
import { BlobServiceClient } from '@azure/storage-blob'
import { DefaultAzureCredential } from '@azure/identity'

const blobServiceClient = new BlobServiceClient(
  `https://${process.env.AZURE_STORAGE_ACCOUNT_NAME}.blob.core.windows.net`,
  new DefaultAzureCredential()
)
```

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

```javascript
async function transferToAzure(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 so each blob path is unique regardless of filename,
  // and so you can reconstruct the path later using only the Uploadcare UUID.
  const blobName = `uploads/${uuid}/${filename}`

  const containerClient = blobServiceClient.getContainerClient(
    process.env.AZURE_STORAGE_CONTAINER_NAME
  )
  const blockBlobClient = containerClient.getBlockBlobClient(blobName)

  await blockBlobClient.uploadData(buffer, {
    blobHTTPHeaders: {
      blobContentType: mimeType,
    },
  })

  return blobName
}
```

Setting `blobContentType` ensures Azure serves the blob with the correct `Content-Type` header when it is accessed directly or through a CDN: without it, browsers may treat the file as `application/octet-stream` regardless of extension.

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 blobName = await transferToAzure(uuid, original_file_url, filename, mime_type)
    console.log(`Transferred ${uuid} to Azure as: ${blobName}`)
    // 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 tells Uploadcare the delivery failed and triggers automatic retries. This covers transient faults — brief Azure Storage unavailability, network timeouts on large files — without requiring a separate retry queue on your end.

**Handing off a transformed version**

The webhook payload only carries `original_file_url`, the untouched upload. If your Azure container 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 blobName = await transferToAzure(uuid, sourceUrl, filename, mime_type)
```

Operations can be stacked, for example `-/resize/1200x/-/format/webp/`. If format is changed, the `blobContentType` field passed to `uploadData()` 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**

Deleting the file right after a successful transfer closes the window during which it is accessible via Uploadcare's CDN and prevents it from counting against your Uploadcare storage quota:

```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 `transferToAzure` 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 for strict control over how long files remain accessible via Uploadcare and to keep storage costs predictable. Choose Option B when you want the simplest possible backend and the expiration window comfortably covers your transfer time.

## Important considerations

**Autostore behavior**

Uploadcare projects have autostore enabled by default, so files are retained indefinitely unless explicitly deleted. To rely on 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 the same event may arrive more than once. The `uploadData` call above is safe to repeat: uploading to the same blob name overwrites the existing blob without returning an error. If your handler also writes to a database, guard those writes with a check on the UUID to avoid creating duplicate records.

**Blob naming**

The example uses `uploads/{uuid}/{filename}` as the blob name. The UUID prefix guarantees uniqueness within the container even when multiple users upload files with identical names, and it gives you a predictable path you can reconstruct from Uploadcare metadata at any point. You can adjust the prefix to fit your access patterns. For instance, organizing by date (`uploads/2024/01/{uuid}/{filename}`) simplifies [Azure Blob lifecycle management policies](https://learn.microsoft.com/en-us/azure/storage/blobs/lifecycle-management-overview), which let you automatically tier or delete blobs based on age.

**Feature availability after file deletion**

Uploadcare's CDN delivery, URL API image transformations, and adaptive bitrate streaming 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 — [Azure CDN](https://learn.microsoft.com/en-us/azure/cdn/cdn-create-a-storage-account-with-cdn), [Azure Front Door](https://learn.microsoft.com/en-us/azure/frontdoor/), or [Shared Access Signatures](https://learn.microsoft.com/en-us/azure/storage/common/storage-sas-overview) for private containers — before removing files from Uploadcare.

## Summary

This flow lets you use Uploadcare as an upload and processing layer while keeping files in Azure Blob Storage. Uploadcare handles ingestion and emits a webhook; your backend authenticates to a Storage Account, downloads the file, writes it to a container as a block blob, 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 Google Cloud Storage, see [Google Cloud Storage](/docs/google-cloud-storage/). For Amazon S3, see [Direct uploads to S3](/docs/aws-s3-storage/) and [Copy files to S3](/docs/s3-integration/).