# How to Download a File Attachment

When an incoming webhook payload includes file attachments, you need to request a short-lived signed URL before you can download the file. This is a two-step process: identify the attachment IDs from the webhook, then exchange them for signed download URLs.

## Attachments in the Webhook Payload

Messages with attachments include an `attachments` array in `webhook.data`. Each entry has a `type` field — only `"file"` attachments require a signed URL. `"link"` attachments have a `link` field that is a directly accessible URL.

| Field | Description |
| --- | --- |
| `id` | The attachment ID — use this to request a signed download URL |
| `type` | `"file"` for uploaded files, `"link"` for web URLs |
| `link` | For `type: "link"`: the URL itself. For `type: "file"`: the internal storage path (not directly accessible) |
| `filename` | Original filename (e.g. `document.pdf`) |
| `mime_type` | MIME type (e.g. `application/pdf`) |
| `length_in_bytes` | File size in bytes |

**Example webhook payload with a file attachment:**

```json
{
  "event": "message.posted.to.channel",
  "data": {
    "id": "message-uuid",
    "conversation_id": "channel-uuid",
    "attachments": [
      {
        "id": "attachment-uuid",
        "type": "file",
        "filename": "report.pdf",
        "mime_type": "application/pdf",
        "length_in_bytes": 204800
      }
    ]
  }
}
```

## Get a Signed Download URL (Single File)

```
GET /v5/attachments/download/signedurl/:attachment_id
Authorization: Bearer <your-pat>
```

**Response** — a plain string containing the signed URL:

```
"https://storage.example.com/files/report.pdf?X-Amz-Signature=..."
```

The signed URL is temporary. Download the file promptly after receiving it.

**Example:**

```bash
curl -X GET https://api.carbonvoice.app/v5/attachments/download/signedurl/attachment-uuid \
  -H "Authorization: Bearer <your-pat>"
```

## Get Signed Download URLs in Bulk

If a message has multiple attachments, fetch all signed URLs in a single request:

```
POST /v5/attachments/download/signedurl/bulk
Content-Type: application/json
Authorization: Bearer <your-pat>
```

```json
{
  "ids": ["attachment-uuid-1", "attachment-uuid-2"]
}
```

**Response** — an array of objects, one per requested ID:

| Field | Description |
| --- | --- |
| `attachment_id` | The attachment ID from the request |
| `signed_url` | The signed download URL for that attachment |

```json
[
  {
    "attachment_id": "attachment-uuid-1",
    "signed_url": "https://storage.example.com/files/report.pdf?X-Amz-Signature=..."
  },
  {
    "attachment_id": "attachment-uuid-2",
    "signed_url": "https://storage.example.com/files/photo.jpg?X-Amz-Signature=..."
  }
]
```

## Full Example — Webhook Handler

```javascript
async function handleWebhook(webhook) {
  const fileAttachments = (webhook.data.attachments ?? []).filter(
    (a) => a.type === 'file',
  );

  if (fileAttachments.length === 0) return;

  const ids = fileAttachments.map((a) => a.id);
  const response = await fetch(
    'https://api.carbonvoice.app/v5/attachments/download/signedurl/bulk',
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.CV_PAT}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ ids }),
    },
  );

  const signedUrls = await response.json();
  for (const { attachment_id, signed_url } of signedUrls) {
    const file = await fetch(signed_url);
    // process file.body stream...
  }
}
```

## Related

- [How to Attach a File](./how-to-attach-a-file.md)
- [How to Handle Incoming Webhook Payloads](./how-to-handle-incoming-webhook-payloads.md)
