NewConnections and customer replies to invoices
BeeL
Get startedMulti-NIFVeriFactuRulesStripeAPI referenceChangelog

Read customer replies

How replies of customers about your invoices reach you, through the invoice.reply.received webhook and the invoice-replies endpoints, what is masked in each, and how to download their attachments.


A reply is a message in which a customer answers about one of your invoices. BeeL. stores replies from two sources and gives them one shape:

Each reply has a channel (the medium the customer wrote in), a connection_id (null when you registered it) and authenticated_by, which says what links the message to its invoice: REPLY_TOKEN for an answer to an invoice email, API_KEY for one you registered. It never says who wrote it.

Get them as they arrive

Subscribe a webhook to invoice.reply.received. It is delivered when a reply is stored, also when you register it yourself. Subscribing needs the invoice-replies:read scope, and the event only reaches the endpoints of the account that owns the company, never those of an account that manages it.

{
  "id": "9b2f4c1d-7e3a-4b8c-9d0e-1f2a3b4c5d6e",
  "type": "invoice.reply.received",
  "created_at": "2026-11-03T09:12:45Z",
  "api_version": "2025-01",
  "livemode": true,
  "company_id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
  "nif": "B12345674",
  "data": {
    "reply_id": "3f0c9a1e-6b2d-4c8f-a1e5-7d9b0c2e4f61",
    "channel": "EMAIL",
    "connection_id": "8c3e1f7a-2b4d-4e6f-9a1c-5d7b3e9f2a41",
    "invoice_id": "b71d5e2a-9c3f-4a7b-8e10-2f6d4c8a9b13",
    "invoice_number": "F2026-0142",
    "authenticated_by": "REPLY_TOKEN",
    "customer_id": "7a2c9e14-3b5d-4f6a-8c0e-1d2b3a4c5e6f",
    "received_at": "2026-11-03T09:12:44Z",
    "sender": { "address": "[EMAIL]" },
    "subject": "RE: Factura F2026-0142",
    "excerpt": "Hola, el CIF que aparece no es el nuestro, es [NIF]. ¿Podéis corregirla?",
    "attachments": []
  }
}

The event page has the full schema. Events do not necessarily arrive in the order they happened. Deduplicate and verify the signature as for any other webhook.

Read them on demand

  • List the replies of a company, most recent first, filtering by invoice_id, connection_id, customer_id, channel (a comma-separated list), from, to and sort_order. A reply you registered has no connection, so a connection_id filter never returns it.
  • Get one reply with its full text.

Both need invoice-replies:read. See Permissions.

What is masked

Replies carry personal data of your customers, and often of third parties. The webhook and the list mask what they show, and only the detail of one reply gives you the original text.

FieldWebhook and listDetail of one reply
excerptFirst paragraph of the text, up to 280 characters, without the quoted original email. Tax IDs, IBANs, email addresses and phone numbers become [NIF], [IBAN], [EMAIL] and [PHONE].The same excerpt.
textNot included.The full text, unmasked, up to 20,000 characters; is_text_truncated tells you when it was cut.
subjectMasked in the same way.The same masked subject.
sender.address[EMAIL], [PHONE] or [ID], by channel.The same masked address.
sender.nameNever included.The display name, when the channel carries one.
external_idNot included.The identifier of the message on its channel.
attachmentsid, url, content_type, size_bytes and file_expires_at.Also the filename.

BeeL. does not store the files of the attachments: see Attachments.

sender is the address the message says it comes from, and it is not verified. Do not use it alone to authorize a change.

Attachments

Each attachment of a reply carries an id, a url, its content_type, its size_bytes and file_expires_at, the same in the invoice.reply.received webhook, in the list and in the detail of the reply. The detail also gives the filename.

"attachments": [
  {
    "id": "5b0e2c7a-91d4-4f3e-8a6b-2c1d9e0f7a34",
    "url": "/v1/companies/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f/invoice-replies/3f0c9a1e-6b2d-4c8f-a1e5-7d9b0c2e4f61/attachments/5b0e2c7a-91d4-4f3e-8a6b-2c1d9e0f7a34",
    "content_type": "application/pdf",
    "size_bytes": 48213,
    "file_expires_at": "2026-11-18T10:15:00Z"
  }
]

BeeL. does not store the file. It can be downloaded for a few days after the reply arrives, until around file_expires_at, through a temporary link that BeeL. requests when you ask for it.

Download a file

  1. Call the url of the attachment with your API key. It is relative to the API base URL, https://app.beel.es/api, and points to GET /v1/companies/{company_id}/invoice-replies/{reply_id}/attachments/{attachment_id}, which needs invoice-replies:read.
  2. The response carries download_url, the temporary link to the file, and download_url_expires_at, about an hour later.
  3. Download download_url before download_url_expires_at, without your API key: the link goes to the file, not to the BeeL. API, so do not send it the Authorization header.
# 1. Ask for a download link
curl "https://app.beel.es/api$ATTACHMENT_URL" \
  -H "Authorization: Bearer $BEEL_API_KEY"

# 2. Download the file with the download_url of the response, without the API key
curl -o justificante-transferencia.pdf "$DOWNLOAD_URL"

Do not keep download_url: when you need the file again, call url again for a new link, while the file is still available.

When there is no file

ValueMeaning
file_expires_at: nullThe attachment never had a file: it was registered with POST .../invoice-replies, which stores only its description. Its download_url is always null.
download_url: nullThere is no file to download, and download_url_expires_at is null too. Either the attachment never had one, or its file is no longer available. That is the case after file_expires_at, and it can happen earlier, because file_expires_at is an estimate. Calling again does not bring the file back.
attachments: nullThe reply is past its retention of 12 months after received_at and its attachments were erased. Asking for one of them answers 404 INVOICE_REPLY_ATTACHMENT_NOT_FOUND.

Errors

StatusCodeWhat to do
404INVOICE_REPLY_NOT_FOUNDThe reply does not exist, belongs to another company or environment, or was deleted.
404INVOICE_REPLY_ATTACHMENT_NOT_FOUNDThe reply has no attachment with that id, or its attachments were erased. Use the url as it comes in the reply.
429TOO_MANY_REQUESTSToo many download links were requested in a short time. Wait the seconds in the Retry-After header and call again.
429RATE_LIMIT_EXCEEDEDYour key went over its rate limit. Wait the seconds in Retry-After and call again.
502EXTERNAL_SERVICE_ERRORThe link could not be requested. It is transient: call again later, before file_expires_at.

A missing scope or access answers 403, as in Permissions.

Keep the files you need

BeeL. never keeps a copy of a file. If you need to keep an attachment, download it when invoice.reply.received arrives: for each attachment whose file_expires_at is not null, call its url and save the file in your own storage. Once the file is gone, neither BeeL. nor the API can recover it.

Fields that can be null

invoice_id, invoice_number and customer_id can be null (a customer is null when the invoice has a one-off recipient), and subject is null on every channel but EMAIL. After the retention period the content fields are null too. Handle null in each of them.