Primeiros passos
Do zero ao primeiro push com prova de entrega — pelo SDK React Native ou direto pela API, com os dois comandos que você copia e roda.
Primeiros passos
Ao fim desta página você vai ter um aparelho registrado, um push enviado e — a parte que distingue o PushMesh — a prova de que a mensagem chegou ao aparelho, não apenas de que o provedor aceitou entregá-la.
Guarde estas duas palavras, porque o resto da documentação as usa o tempo todo:
| Número | O que significa |
|---|---|
successful | o FCM/APNs aceitou a mensagem. É o que todo serviço de push sabe dizer. |
recebidos | o aparelho devolveu a prova de que a notificação chegou. É o que você veio buscar aqui. |
Os endereços
| Endereço | Para que serve |
|---|---|
https://api.pushmesh.io | a API pública. É o endereço de tudo nesta referência: o seu servidor, o SDK e qualquer cliente próprio falam aqui |
https://app.pushmesh.io | o painel — onde nasce o aplicativo, a chave e as credenciais de entrega |
https://pushmesh.io | o site |
Passe sempre
baseUrlpara o SDK. A versão publicada hoje no npm (@pushmesh/sdk@0.7.2) traz outro endereço embutido como padrão para quandobaseUrlé omitido, e esse endereço não está no ar. Omitindo o campo, o aparelho não registra e você não recebe erro nenhum do servidor — a chamada nem chega a sair. Os exemplos desta página passambaseUrlexplicitamente, e você deve fazer o mesmo até uma versão nova do pacote corrigir o padrão.
Antes de começar
Três coisas, e todas vêm do painel em https://app.pushmesh.io:
| O que | Para que serve | Onde pegar |
|---|---|---|
app_id (UUID) | identifica o seu aplicativo em toda chamada | o painel mostra no cadastro do aplicativo |
Chave da API pm_live_… | autentica as chamadas do seu servidor | aparece uma única vez, no momento em que o aplicativo é criado |
| Credencial de entrega (FCM e/ou APNs) | é o que permite o push sair de verdade para o aparelho | painel → configurações do aplicativo → FCM / APNs |
A credencial de entrega é um passo obrigatório, e o que ela exige não é óbvio. No Android é o arquivo
.jsonde uma conta de serviço do Firebase (a chave de servidor legada não serve); no iOS é a chave.p8do APNs mais Key ID, Team ID, Bundle ID e a escolha do ambiente — onde mora a armadilha clássica (TestFlight é produção). Sem ela, o disparo responde200, orecipientsvem certo e nada sai. O que ter em mãos, campo a campo, está em Credenciais de entrega — vale ler antes de criar a conta.
A chave aparece uma vez. No banco fica apenas um resumo criptográfico dela; ninguém — nem o suporte — consegue mostrá-la de novo. Perdeu, rotacione (a rotação mantém a anterior válida por 24 h, então nada quebra).
Nunca embuta a chave
pm_live_no aplicativo publicado. Um APK ou IPA é um arquivo que qualquer pessoa baixa e abre. As rotas usadas pelo aparelho (registro, recibo, In-App) foram desenhadas sem credencial exatamente por isso: oapp_idviaja no corpo, e o que protege é a validação do aplicativo mais um teto por endereço de origem.
Guarde os dois valores no ambiente para poder copiar e colar os comandos abaixo:
export PUSHMESH_KEY="pm_live_a1b2c3d4_coloque_aqui_os_43_caracteres_da_sua_chave"
export APP_ID="00000000-0000-0000-0000-000000000000"
1. Confirme que você está falando com a API
curl -s https://api.pushmesh.io/
{ "servico": "pushmesh", "versao": "<versão do serviço>", "git": "<commit no ar>" }
Agora confirme que a sua chave é aceita. Esta rota não precisa de nenhum parâmetro — ela devolve como o seu aplicativo está configurado para receber confirmações:
curl -s -H "Authorization: Basic $PUSHMESH_KEY" \
https://api.pushmesh.io/api/v1/confirmacoes
{ "modo": "consulta", "fila_pendente": 0, "streams_ativos": 0 }
401→ a chave está ausente, incompleta ou não é desta conta.403→ a chave é válida, mas o aplicativo está pausado.200→ pode seguir.
Repare também no cabeçalho X-Pm-Request-Id da resposta (use curl -i para
vê-lo). Ele vem em toda resposta, de sucesso ou de erro, e é o
identificador que o suporte pede para achar a sua chamada.
2. Registre um aparelho
Registrar é dizer ao PushMesh: “este token de push pertence a este aplicativo”.
O que volta é um player_id — o identificador público do aparelho, o que você
usa para mandar mensagem para ele.
Caminho A — SDK React Native (recomendado)
O pacote está publicado no npm e faz sozinho o registro, a renovação do token, o recibo de entrega e o recibo de clique.
npm install @pushmesh/sdk @notifee/react-native @react-native-async-storage/async-storage
cd ios && bundle exec pod install # iOS
import PushMesh from '@pushmesh/sdk';
// uma vez, no boot do app
await PushMesh.init({
appId: 'SEU_APP_ID',
baseUrl: 'https://api.pushmesh.io', // obrigatório na 0.7.2 — veja a nota acima
appVersion: '1.0.0',
});
// no login do seu app, para poder disparar por identificador de usuário
await PushMesh.login('usuario-123');
// o identificador do aparelho, se você quiser mandar direto para ele
const playerId = await PushMesh.getPlayerId();
Peça a permissão — o init não pede por você
Este é o segundo passo obrigatório que costuma faltar. O SDK não solicita a
permissão de notificação no init, de propósito: quem escolhe o momento de
pedir é o seu aplicativo, não a biblioteca. Em Android 13 ou mais novo e em
qualquer iOS, sem esse pedido o aparelho registra normalmente, o disparo responde
successful: 1 — e recebidos fica em 0 para sempre.
// no momento certo do seu fluxo (nunca no primeiro segundo do app)
if (await PushMesh.permissions.canRequestNatively()) {
// ainda dá para o pedido nativo: um toque, sem sair do app
await PushMesh.permissions.checkAndReport();
} else {
// já houve uma decisão antes — só os Ajustes do sistema revertem
await PushMesh.permissions.openNotificationSettings();
}
canRequestNatively() é o “botão inteligente”: ele responde true só enquanto o
sistema ainda aceita o diálogo nativo. Mandar para os Ajustes quem nunca viu o
pedido é péssimo — no iOS, um aplicativo que nunca pediu permissão sequer tem
página nos Ajustes, e a pessoa cai na raiz e se perde.
Honestidade sobre o
notification_types: o SDK lê a permissão real do sistema e reporta a mudança ao servidor, mas nenhuma rota desta API grava hoje os estados “bloqueou” (-2) ou “nunca pediu” (0). Um aparelho registrado nasce comnotification_types: 1e assim permanece; o que muda ao longo da vida dele é oinvalid_identifier. Para “consigo alcançar este aparelho?”, a pergunta verdadeira hoje éinvalid_identifier === false.
O que o SDK passa a fazer sem mais nenhuma linha sua:
- registra o aparelho e mantém o token atualizado;
- exibe a notificação recebida;
- manda o recibo de entrega quando o push chega e o recibo de clique
quando a pessoa toca — é isso que alimenta o número
recebidos; - guarda os recibos numa fila local quando o aparelho está sem rede, e os reenvia no próximo arranque.
Se o seu aplicativo já tem Firebase próprio, passe o token que você já obtém
(init({ appId, getToken })) e o resto continua igual. Há um diagnóstico de
linha de comando que confere a integração de fora:
npx pushmesh-doctor --base-url https://api.pushmesh.io --app-id "$APP_ID"
Caminho B — direto pela API
É o caminho de quem escreve o próprio cliente (outra plataforma, um aparelho
embarcado, um teste). Esta rota não leva Authorization:
curl -s -X POST https://api.pushmesh.io/api/v1/players \
-H "Content-Type: application/json" \
-d '{
"app_id": "'"$APP_ID"'",
"device_type": 1,
"identifier": "TOKEN_DE_PUSH_DO_APARELHO",
"external_user_id": "usuario-123",
"app_version": "1.0.0",
"language": "pt",
"timezone": -10800
}'
{ "success": true, "id": "0198e7c4-77a1-7bd2-b0f1-6b1d3f2a9c40" }
Esse id é o player_id. Guarde-o.
| Campo | Tipo | Obrigatório | Limite | O que faz |
|---|---|---|---|---|
app_id | UUID | sim | — | o aplicativo dono do aparelho. Inexistente ⇒ 400; pausado ⇒ 403. |
device_type | inteiro | sim | 0 iOS · 1 Android · 5 Chrome · 7 Safari · 8 Firefox | a plataforma. Outro valor ⇒ 400 nomeado. |
identifier | string | sim | 194 bytes | o token de push do aparelho. Vazio ou maior que o limite ⇒ 400. |
external_user_id | string | não | 128 bytes; até 10 aparelhos por valor | o seu identificador de pessoa — é o que permite disparar sem conhecer o token. |
app_version | string | não | — | versão do seu aplicativo. |
device_model | string | não | — | modelo do aparelho. |
tags | objeto JSON | não | 2 KB já serializado | pares livres para segmentar. Lista ou string ⇒ 400. |
language | string | não | 32 bytes | idioma do aparelho. |
timezone | inteiro | não | ±86400 | fuso em segundos (-10800 = UTC−3). Nome de fuso aqui ⇒ 400. |
Três comportamentos que economizam a sua primeira tarde:
- O sucesso é
200, não201— inclusive na primeira vez. - Registrar de novo o mesmo token devolve o mesmo
player_id. A rota é idempotente por aplicativo + token: chame a cada abertura do app, sem medo. - No
POST, campo omitido apaga o valor gravado paraexternal_user_id,app_version,device_model,tags,languageetimezone. Mande sempre o retrato completo do aparelho — ou faça alterações pontuais peloPUT /api/v1/players/{id}, onde campo ausente nunca apaga nada. Os detalhes dos dois verbos estão em Aparelhos.
Ensaio sem aparelho: um
identifierque começa comtest:(por exemplotest:ok) registra normalmente e devolve umplayer_id— serve para validar a sua integração de registro. Mas ele é marcado como aparelho de ensaio e fica de fora de qualquer disparo, de propósito. Para ver o recibo de verdade na etapa 4, use um aparelho real.
3. Dispare o push
Agora sim com a chave, porque quem dispara é o seu servidor:
curl -s -X POST https://api.pushmesh.io/api/v1/notifications \
-H "Authorization: Basic $PUSHMESH_KEY" \
-H "Content-Type: application/json" \
-d '{
"app_id": "'"$APP_ID"'",
"include_player_ids": ["0198e7c4-77a1-7bd2-b0f1-6b1d3f2a9c40"],
"headings": { "en": "Order shipped", "pt": "Pedido a caminho" },
"contents": { "en": "It arrives tomorrow", "pt": "Chega amanhã" }
}'
{ "id": "0198e7d1-1c40-7f2a-9b31-77aa10c4e001", "recipients": 1 }
Esse id é o disparo. Ele é também o pm_msg_id que viaja dentro do
payload do push, e é por ele que o recibo do aparelho encontra o caminho de
volta.
Regras mínimas do corpo:
| Campo | Tipo | Obrigatório | O que faz |
|---|---|---|---|
app_id | UUID | sim | precisa ser o aplicativo da chave, senão 401. |
contents | objeto {idioma: texto} | sim | o corpo da mensagem. Objeto vazio ⇒ 400. |
headings | objeto {idioma: texto} | não | o título. |
| um alvo | — | sim | include_player_ids (até 2.000), include_external_user_ids (até 2.000) ou included_segments (até 10 nomes). Nenhum ou mais de um ⇒ 400. |
Para um envio amplo, troque o alvo por um segmento:
"included_segments": ["Subscribed Users"]
Os nomes aceitos são Subscribed Users (e o sinônimo Total Subscriptions),
Active Users (vistos nos últimos 7 dias) e Engaged Users (devolveram recibo
nos últimos 7 dias). Nome desconhecido responde 400 listando os válidos.
Ensaie antes, se quiser. Com o cabeçalho X-Dry-Run: true a chamada
resolve o público de verdade, devolve recipients real e não entrega nada,
nem grava nada:
curl -s -X POST https://api.pushmesh.io/api/v1/notifications \
-H "Authorization: Basic $PUSHMESH_KEY" \
-H "X-Dry-Run: true" \
-H "Content-Type: application/json" \
-d '{"app_id":"'"$APP_ID"'","included_segments":["Subscribed Users"],"contents":{"pt":"teste"}}'
{ "id": "", "recipients": 12840, "dry_run": true }
Duas respostas 200 que parecem erro e não são
{ "id": "", "recipients": 0, "errors": ["All included players are not subscribed"] }
Ninguém do alvo estava alcançável. Não é 4xx e não adianta repetir: ou os
identificadores não existem neste aplicativo, ou os aparelhos não estão
inscritos, ou eram aparelhos de ensaio.
{
"id": "0198e7d1-1c40-7f2a-9b31-77aa10c4e001",
"recipients": 1,
"errors": { "invalid_player_ids": ["11111111-1111-1111-1111-111111111111"] }
}
Envio parcial: os válidos foram, e a resposta diz quais ficaram de fora. Repare
que aqui errors é um objeto, e no caso anterior era um array — se
você desserializa para um tipo fixo, aceite a união.
4. Veja o recibo
curl -s -H "Authorization: Basic $PUSHMESH_KEY" \
"https://api.pushmesh.io/api/v1/notifications/0198e7d1-1c40-7f2a-9b31-77aa10c4e001?app_id=$APP_ID"
{
"id": "0198e7d1-1c40-7f2a-9b31-77aa10c4e001",
"successful": 1,
"failed": 0,
"errored": 0,
"remaining": 0,
"converted": 0,
"recebidos": 1,
"queued_at": 1756300000,
"completed_at": 1756300003,
"contents": { "en": "It arrives tomorrow", "pt": "Chega amanhã" },
"platform_delivery_stats": {
"android": { "successful": 1, "failed": 0 },
"ios": { "successful": 0, "failed": 0 }
},
"pm_metricas": { "via": "rest", "cobertura_recibo_pct": 100.0, "ttfd_ms": null, "duracao_ms": 3000 }
}
O exemplo acima está abreviado: a resposta traz também o título, o alvo e a janela de agendamento. A lista completa está em Recibos de entrega.
| Campo | O que significa |
|---|---|
successful | o provedor aceitou. Não é prova de chegada. |
failed | rejeição permanente (token morto, por exemplo). |
errored | as três tentativas de envio se esgotaram. |
remaining | ainda em processamento. Tem que chegar a zero. |
converted | cliques confirmados pelo aparelho. 0 quer dizer “ninguém tocou”. |
recebidos | quantos aparelhos provaram que receberam. |
pm_metricas.cobertura_recibo_pct | recebidos ÷ successful, em porcentagem. |
Duas honestidades sobre esses números:
- A cobertura raramente chega a 100%. Aparelho desligado, sem rede ou com o aplicativo desinstalado não devolve prova nenhuma. Um número abaixo de 100% é o retrato real da sua base, não defeito da plataforma.
recebidos: nullnão é zero. No plano gratuito o campo vemnullacompanhado de um blocorecebidos_gateexplicando que a confirmação real de entrega é recurso do plano pago. Se o seu código fazrecebidos ?? 0, o limite comercial vira “zero entregas” no seu painel.
5. Quem manda o recibo
Se você está usando o SDK, ninguém: ele já mandou. Esta seção é para quem escreve o próprio cliente.
Todo push sai com dois campos dentro de data: pm_msg_id (o disparo) e
pm_rcpt (uma prova criptográfica válida só para aquele disparo naquele
aparelho). Quando a notificação chega, o seu cliente ecoa os dois:
curl -s -X POST https://api.pushmesh.io/api/v1/receipts \
-H "Content-Type: application/json" \
-d '{
"app_id": "'"$APP_ID"'",
"notification_id": "PM_MSG_ID_QUE_VEIO_NO_PUSH",
"player_id": "0198e7c4-77a1-7bd2-b0f1-6b1d3f2a9c40",
"rcpt": "PM_RCPT_QUE_VEIO_NO_PUSH",
"evento": "recebido"
}'
{ "ok": true }
eventoaceitarecebido(padrão) eclique. Umcliquesem recibo anterior conta as duas coisas — ninguém toca no que não chegou.- Reenviar o mesmo recibo responde
{"ok":true,"duplicado":true}: pode descartar o item da sua fila. {"ok":true,"tardio":true}quer dizer “contei, por um caminho mais lento” — também é sucesso, e não deve ser tratado como duplicado.- Não tente calcular o
rcpt. A chave que o gera nunca sai do servidor; ecoe exatamente o que veio no push. Prova errada responde403, sempre com o mesmo texto.
6. Se nada chegou
Percorra nesta ordem — é a ordem em que as coisas costumam falhar:
| Sintoma | Causa provável |
|---|---|
| Nenhum aparelho registrado, e nenhum erro na sua rede | o SDK está sem baseUrl e falou com o endereço padrão dele, que não está no ar. Passe baseUrl: 'https://api.pushmesh.io' no init. |
recipients: 0 com id vazio | o alvo não existe neste aplicativo, os aparelhos não estão inscritos, ou eram aparelhos de ensaio (test:). |
recipients bom, successful: 0, remaining: 0 | falta a credencial de entrega (FCM/APNs) no painel, ou ela está incorreta. Veja Credenciais de entrega. |
successful bom, recebidos: 0 | o push saiu, mas o aparelho não devolveu prova: aplicativo sem permissão de notificação, desinstalado, sem rede — ou o seu cliente próprio não está chamando a rota de recibos. |
403 com “app pausado” | o aplicativo está pausado na conta. |
422 falando em bytes | o payload renderizado passou de 3.891 bytes. O limite inclui 96 bytes que o servidor injeta (pm_msg_id + pm_rcpt); a mensagem de erro diz quantos bytes encurtar. |
422 falando em plano e em usuários ativos | a conta está no plano gratuito e passou da franquia mensal. O envio é recusado inteiro, de propósito, e repetir não resolve — o explain traz o teto, o número atual e o preço de sair do gratuito. Veja Limites. |
429 | você bateu num teto. Veja Autenticação e erros. |
| Qualquer erro que você não entendeu | leia explain.causa e explain.como_corrigir no corpo, e guarde o request_id. |
O que fazer em seguida
| Se você quer… | Vá para |
|---|---|
| entender a chave, a rotação e todos os erros | Autenticação e erros |
| ter as credenciais de FCM e APNs em ordem | Credenciais de entrega |
| todos os campos do disparo (agendamento, imagem, prioridade, idempotência) | Enviar push |
| a prova de entrega por dentro, o histórico e os números por janela | Recibos de entrega |
| gerir a base de aparelhos e exportá-la | Aparelhos |
| ser avisado em vez de perguntar | Webhooks |
| mensagens dentro do aplicativo | Mensagens In-App |
| migrar de outro serviço de push | Migrando de outro provedor |
| todos os tetos, com números | Limites |
| retenção, dados pessoais e exclusão | Dados, retenção e privacidade |
| saber o que pode mudar sem aviso | Versionamento |