Developer Portal
How-to guides

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.

FieldDescription
idThe attachment ID — use this to request a signed download URL
type"file" for uploaded files, "link" for web URLs
linkFor type: "link": the URL itself. For type: "file": the internal storage path (not directly accessible)
filenameOriginal filename (e.g. document.pdf)
mime_typeMIME type (e.g. application/pdf)
length_in_bytesFile size in bytes

Example webhook payload with a file attachment:

{
  "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:

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>
{
  "ids": ["attachment-uuid-1", "attachment-uuid-2"]
}

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

FieldDescription
attachment_idThe attachment ID from the request
signed_urlThe signed download URL for that attachment
[
  {
    "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

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 Download a File Attachment | Carbon Voice Developer Portal