Armazenamento próprio (BYO Storage)
Com o armazenamento próprio (BYO Storage, de “Bring Your Own”), o Turbo Notify guarda as mídias recebidas e as fotos de perfil de contatos diretamente no seu bucket. Sem essa configuração, as mídias recebidas não ficam disponíveis para download.
Como funciona o armazenamento próprio
Quando um contato envia uma imagem, vídeo, áudio, documento ou figurinha para o seu número de WhatsApp, o Turbo Notify:
- Recebe a mídia do WhatsApp.
- Envia o arquivo para o seu bucket (pela API S3).
- Devolve uma URL pré-assinada diretamente no objeto de mídia (
image.url,video.url, …) quando você consultaGET /v1/numbers/{ref}/messages/{id}. - Envia o webhook
message.receivedcom os campos de mídia disponíveis.
Sem armazenamento próprio configurado, o campo kind retorna "unavailable" com reason: "no_storage". O conteúdo da mídia não é guardado.
Provedores suportados
O Turbo Notify usa a API HTTP do S3, o mesmo protocolo suportado pelos três provedores:
| Provedor | Notas |
|---|---|
| Cloudflare R2 | Sem custo de tráfego de saída; boa escolha quando as mídias são baixadas com frequência. |
| Supabase Storage | Útil se você já usa Supabase no seu projeto. |
| AWS S3 | Há custo de tráfego de saída. |
Como configurar
O caminho é o mesmo nos três provedores: um bucket privado, uma chave de acesso e o formulário em Armazenamento (abre em nova aba) . Abaixo está o caminho em cada provedor. As telas deles mudam com o tempo: se um nome não bater, procure o equivalente.
O bucket deve ser privado, sem acesso público. O Turbo Notify acessa o conteúdo com as credenciais que você informar, não por URLs públicas. A chave de acesso precisa destas permissões. No R2 e na AWS, restrinja a chave a esse bucket; a chave do Supabase não pode ser restringida a um bucket:
s3:PutObjects3:GetObjects3:DeleteObjects3:ListBucketCloudflare R2
- Entre em
dash.cloudflare.come abra R2 object storage. Na primeira vez, ative o R2: a Cloudflare pede um cartão e o aceite dos termos antes de liberar o R2. - Logo depois de ativar, o endereço S3 da conta pode levar alguns minutos para responder. Se o teste de conexão falhar nesse intervalo, espere e tente de novo.
- Clique em Create bucket, escolha o nome, deixe a localização em Automatic e não ligue o acesso público. Não use jurisdição específica (UE ou FedRAMP): o Turbo Notify só aceita o endereço padrão da conta.
- Volte para R2 object storage > Overview. Em API Tokens, clique em Manage e depois em Create Account API token (o Create User API token também serve). Escolha a permissão Object Read & Write e limite o token ao bucket.
- A tela do token mostra três valores. Use o Access Key ID e o Secret Access Key. O Token value (o texto longo que começa com
cfat_oucfut_) não é usado no Turbo Notify. O secret aparece uma vez só: copie antes de sair da tela. - O Account ID tem 32 caracteres e fica em R2 object storage > Overview, em Account details. É o mesmo valor que aparece no endereço S3 da conta (
https://<Account ID>.r2.cloudflarestorage.com).
Supabase Storage
- Abra o projeto em
supabase.com/dashboard, entre em Storage e crie o bucket com New bucket, deixando Public bucket desligado. - Em Storage, abra a página S3 (em alguns painéis ela fica em Settings), procure S3 Access keys e clique em New access key. Copie o Access key ID e o Secret access key: o secret aparece uma vez só. A chave S3 do Supabase vale para todos os buckets do projeto, não só para este.
- O Project Ref são as 20 letras minúsculas do endereço do projeto (
https://<Project Ref>.supabase.co), também no endereço do painel (supabase.com/dashboard/project/<Project Ref>) e na Project URL do diálogo Connect.
AWS S3
- Entre em
console.aws.amazon.com(se ainda não tiver conta, crie uma: a AWS pede um cartão), abra S3 e clique em Create bucket, escolha o nome e a região, e mantenha Block all public access ligado. - Em IAM > Users, crie um usuário sem acesso ao console e anexe esta política (troque
NOME-DO-BUCKET):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::NOME-DO-BUCKET" }, { "Effect": "Allow", "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"], "Resource": "arn:aws:s3:::NOME-DO-BUCKET/*" } ]}- No usuário, abra Security credentials e clique em Create access key. Escolha Application running outside AWS e copie o Access key ID e o Secret access key. O secret aparece uma vez só.
Preencha no dashboard
Acesse Armazenamento (abre em nova aba) e preencha:
| Campo | Valor |
|---|---|
| Provedor | AWS S3, Cloudflare R2 ou Supabase Storage |
| Bucket | Nome do bucket criado |
| Região | Região do bucket (por exemplo us-east-1). No R2 é sempre auto e no Supabase, us-east-1 |
| Access Key ID | A chave de acesso criada no provedor (no formulário aparece como R2 Access Key ID ou Supabase S3 Access Key ID; no R2, é o Access Key ID, nunca o Token value) |
| Secret Access Key | A chave secreta correspondente |
| Cloudflare Account ID / Supabase Project Ref | Só para R2 e Supabase, respectivamente: o endereço S3 é montado a partir desse valor |
Teste a conexão e salve
Antes de salvar, clique em Testar conexão. O Turbo Notify verifica o bucket (HEAD), grava um objeto temporário (PUT), lê esse objeto de volta (HEAD) e o remove (DELETE), validando as 4 permissões necessárias (ListBucket/PutObject/GetObject/DeleteObject). Cada verificação aparece numa lista com o seu resultado, e o botão de salvar só é liberado quando as quatro passam. Depois de salvo, as mídias passam a ser armazenadas imediatamente.
Se alguma verificação falhar:
- Chave recusada: no R2, confira se você usou o Access Key ID e não o Token value.
- Bucket não encontrado: o nome precisa ser exato e o bucket tem de estar na conta da chave.
- R2 recém-ativado: espere alguns minutos e teste de novo.
- Falha em uma só permissão: revise a política ou a permissão do token, que deve incluir leitura, escrita, exclusão e listagem.
Comportamento após a configuração
Mídias recebidas
Toda mídia recebida após a ativação é copiada automaticamente para o seu bucket. Os campos de mídia em GET /v1/numbers/{ref}/messages/{id} passam a retornar kind: "available", por exemplo:
{ "kind": "available", "url": "https://<bucket>.r2.cloudflarestorage.com/...?X-Amz-...", "expires_at": "2026-04-21T11:00:00Z", "mime_type": "image/jpeg", "size_bytes": 184320}O acesso à mídia expira no expires_at. Para obter uma nova URL, consulte a mensagem novamente dentro do prazo de retenção de conteúdo do seu plano. Depois desse prazo, o Turbo Notify apaga a referência ao arquivo, e o arquivo continua no seu bucket.
Quando as fotos aparecem
As fotos de perfil só são guardadas com o armazenamento ativo. Elas não são baixadas todas de uma vez, para não sobrecarregar o seu número no WhatsApp:
| Foto de | Quando é buscada |
|---|---|
| Seus números | Na hora, quando o número conecta ou quando você configura o armazenamento. Se a primeira tentativa não der certo, ela se repete em até cerca de 6 horas. Aparece no dashboard. |
| Contatos | Na próxima mensagem trocada com o contato (recebida ou enviada, inclusive pela API). Se já houve consulta nas últimas 6 horas, só depois disso. |
| Grupos | Na próxima mensagem de um membro do grupo ou enviada pelo próprio celular. Mensagens enviadas só pela API não disparam a busca. Vale a mesma janela de 6 horas. |
| Foto trocada | Uma foto salva é conferida de novo a cada 7 dias: a dos números sozinha, a de contatos e grupos na primeira mensagem depois disso. |
| Sem foto | Quem não tem foto, ou a esconde nas configurações de privacidade do WhatsApp, não aparece. |
Quando a foto chega ao seu bucket, o webhook contact.picture_changed (contatos) ou group.picture_changed (grupos) é enviado, e GET /v1/numbers/{ref}/contacts/{id}/profile-picture responde com um redirecionamento 302 para a URL pré-assinada. Até lá, a resposta é unavailable.
Mídias anteriores à configuração
Mídias recebidas antes da ativação do armazenamento próprio continuam com kind: "unavailable". Elas não são recuperadas depois: apenas as novas mídias são armazenadas.
Solução de problemas
kind: "unavailable" com reason: "upload_failed"
O envio para o bucket falhou depois de novas tentativas. Possíveis causas:
- Credenciais expiradas ou com permissões insuficientes.
- Bucket apagado ou com política de acesso alterada.
- Cota do bucket atingida (verifique no console do provedor).
Acesse Armazenamento (abre em nova aba) e clique em Testar conexão para diagnosticar.
kind: "unavailable" com reason: "storage_circuit_open"
O seu armazenamento falhou várias vezes seguidas e o envio de mídia para ele foi suspenso temporariamente. O Turbo Notify volta a tentar automaticamente. Quando a conexão for restabelecida, você recebe o webhook storage.recovered.
kind: "pending"
A mídia ainda está sendo enviada para o bucket. Aguarde alguns segundos e consulte GET /v1/numbers/{ref}/messages/{id} novamente. Esse estado só aparece enquanto o envio está em andamento: o webhook message.received só é emitido depois que a mídia fica disponível ou indisponível.
Segurança
- As credenciais são guardadas criptografadas (AES-256-GCM), nunca em texto claro.
- O Turbo Notify nunca expõe credenciais em logs ou respostas.
- URLs pré-assinadas têm validade configurável (padrão: 15 minutos; até 1 hora).
- Recomendamos o privilégio mínimo: no R2 e na AWS, credenciais com acesso restrito ao bucket do Turbo Notify. A chave S3 do Supabase vale para o projeto inteiro: se isso for um problema, use um projeto só para o Turbo Notify.
Relacionado
- Consultar mensagem: campos de mídia com união discriminada por
kind. - Webhooks: Armazenamento e mídia: eventos
storage.degradedestorage.recovered. - Envio de mídia: envio de mídia por URL pública.
- Foto de perfil: redirecionamento para URL pré-assinada.