Envio e confirmações por gRPC
A segunda porta da plataforma — envio de alta frequência como frames numa conexão só, e as confirmações de entrega como stream com ack, em vez de webhook. Mesmo contrato, mesma chave, outro transporte.
Envio e confirmações por gRPC
O REST resolve a maioria das integrações. Esta página é para quem está num dos dois extremos onde ele começa a doer:
- Envio individual de alta frequência — transacional, um push por evento do
seu sistema, milhares por minuto. Cada
POSTpaga handshake, cabeçalho e JSON por viagem; no gRPC os pedidos viram frames numa conexão HTTP/2 que já está de pé. - Confirmações em fluxo contínuo — em vez de receber webhook (e manter um endpoint público, com retry e assinatura), você abre um stream e as confirmações de entrega descem por ele, com ack e retomada por cursor.
As duas portas falam o mesmo contrato do REST — mesmo corpo de disparo, mesmo envelope de erro, mesma chave de API — e são escolhas independentes: dá para enviar por gRPC e receber confirmação por webhook, ou o contrário.
O que o gRPC não muda: a entrega ao aparelho continua sendo APNs/FCM, e o recibo de entrega continua sendo a mesma prova HMAC de sempre. O ganho é no aceite e no transporte da confirmação — não existe mágica na última milha, e desconfie de quem prometer o contrário.
O endpoint
grpc.pushmesh.io:443 (TLS · HTTP/2 · sem proxy de terceiros na frente)
Autenticação em toda chamada, na metadata — a mesma chave do REST:
authorization: Basic pm_live_<kid>_<segredo>
A chave é revalidada a cada pedido dentro do stream: revogou no painel, o stream corta no pedido seguinte — não no reinício da conexão.
O contrato vive em proto/confirmacoes.proto no pacote
pushmesh.confirmacoes.v1, e é congelável: campos só são adicionados,
nunca renumerados ou renomeados. Peça o arquivo ao suporte ou copie da
documentação — ele é a lei da porta.
As duas portas são independentes — leia isto primeiro
O modo grpc das configurações de confirmação controla só como as
confirmações chegam até você. Ele NÃO é pré-requisito para enviar:
| você quer | precisa de |
|---|---|
| enviar por gRPC | nada — só a chave de API na metadata; funciona hoje, com qualquer app |
| receber confirmações por gRPC | ligar o modo grpc nas confirmações do app |
Qualquer combinação vale: enviar por gRPC e confirmar por webhook, enviar por REST e confirmar por stream — escolha por fluxo, não por pacote.
Comece em cinco minutos (testado de verdade)
O exemplo abaixo foi executado contra a produção antes de entrar nesta página
— o aceite, o push no aparelho e o recibo de entrega aconteceram de fato.
Você precisa do grpcurl (ou de qualquer cliente gRPC) e do arquivo
confirmacoes.proto desta documentação, salvo no diretório atual.
python3 - <<'FIM' | grpcurl -proto confirmacoes.proto \
-H 'authorization: Basic pm_live_SEU_KID_SEU_SEGREDO' \
-d @ grpc.pushmesh.io:443 pushmesh.confirmacoes.v1.Envio/Enviar
import json
corpo = {
"app_id": "SEU_APP_ID",
"headings": {"pt-BR": "Enviado por gRPC"},
"contents": {"pt-BR": "O mesmo corpo do REST, por outro cano"},
"include_player_ids": ["ID_DO_PLAYER"],
}
print(json.dumps({"chave": "meu-pedido-1", "notificacao_json": json.dumps(corpo)}))
FIM
A resposta que volta, na mesma conexão:
{
"chave": "meu-pedido-1",
"notificationId": "01a05e29-587e-7e73-b3f4-3df343cfb659",
"recipients": 1
}
Dois comportamentos para provar em seguida, porque são o coração do contrato:
- Idempotência: rode o mesmo comando de novo, com a MESMA
chave. Volta o mesmonotificationId— nenhum push duplicado. (Testado: voltou.) - A prova de entrega continua a mesma: consulte o disparo pelo REST
(
GET /api/v1/notifications/{id}) e vejarecebidoscontar quando o aparelho confirmar. O transporte mudou; a promessa não.
Porta 1 — Envio.Enviar: disparos como stream
service Envio {
rpc Enviar(stream PedidoEnvio) returns (stream Aceite);
}
message PedidoEnvio {
string chave = 1; // idempotência (espelha o Idempotency-Key do REST)
string notificacao_json = 2; // o MESMO corpo do POST /api/v1/notifications
}
notificacao_json é, byte a byte, o corpo que você mandaria no
POST /api/v1/notifications — a validação é a mesma, os erros são os mesmos.
Você não aprende um segundo formato de disparo; aprende um segundo transporte.
Cada pedido volta como Aceite, na mesma conexão:
| campo | o que é |
|---|---|
chave | a sua, ecoada — é como você casa pedido e resposta |
notification_id | o id do disparo aceito |
recipients | o mesmo recipients da resposta REST |
errors_json | o objeto errors do 2xx parcial — é aqui que vem invalid_player_ids |
erro_json | vazio = aceito; senão, o mesmo envelope de erro do REST, com explain |
recusa_http | true = a recusa foi do servidor (limite, validação) — trate como falha de transporte, nunca culpe o aparelho |
Idempotência: mesma chave = mesmo disparo, como no REST. Reconectou e
reenviou o lote em dúvida? O servidor absorve.
Teto: há um limite de streams simultâneos por app — uma chave vazada não abre conexões sem fim. Uma conexão com um stream é o desenho normal; abra mais streams só se medir que precisa.
Porta 2 — Confirmacoes.Fluxo: as confirmações descem por stream
Esta porta substitui o webhook para quem prefere conexão de saída a
endpoint público. Exige ligar o modo nas configurações de confirmação do app
(painel → Confirmações → modo grpc; os modos são consulta, webhook e
grpc). Com o modo errado, a própria porta explica o que ligar — o erro
ensina, como tudo aqui.
service Confirmacoes {
rpc Fluxo(stream DoCliente) returns (stream Evento);
}
O protocolo em três passos:
- Abra o stream e mande
Assinar{ app_id, cursor }.cursor = 0começa da fila pendente; qualquer outro valor retoma do últimoentrega_idreconhecido. - Receba
Evento{ entrega_id, evento, payload_json, canal }. Opayload_jsoné o mesmo corpo do webhook (comcanal: "grpc"), e oentrega_idé o mesmoX-PM-Entrega-Id— se um dia você migrar de webhook para stream, a idempotência do seu lado continua valendo. - Confirme com
Ack{ entrega_id }— ou em lote,Ack{ entrega_ids: […] }. Só o ack marca o evento como entregue. Caiu a conexão? Reconecte com o último cursor reconhecido e o que não foi confirmado desce de novo.
Semântica at-least-once, como o webhook: prepare-se para receber um evento
duas vezes na reconexão, e deduplique por entrega_id.
Eventos que descem pelo stream:
delivery.received · delivery.clicked · notification.completed · teste.ping
O teste.ping das configurações de confirmação também sai por aqui quando o
modo é grpc — é como você prova o encanamento de ponta a ponta antes do
primeiro disparo real.
Recebendo as confirmações em Node.js (porta 2)
Um consumidor completo do stream de confirmações, com ack — o esqueleto que a maioria das integrações usa:
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const def = protoLoader.loadSync('confirmacoes.proto');
const pb = grpc.loadPackageDefinition(def).pushmesh.confirmacoes.v1;
const md = new grpc.Metadata();
md.set('authorization', 'Basic pm_live_SEU_KID_SEU_SEGREDO');
const cliente = new pb.Confirmacoes('grpc.pushmesh.io:443', grpc.credentials.createSsl());
const fluxo = cliente.Fluxo(md);
// 1. assina — cursor 0 começa da fila pendente; guarde o último ack para retomar
fluxo.write({ assinar: { app_id: 'SEU_APP_ID', cursor: 0 } });
fluxo.on('data', (ev) => {
const corpo = JSON.parse(ev.payload_json); // o MESMO corpo do webhook
console.log(ev.evento, corpo); // delivery.received, delivery.clicked…
// processe ANTES do ack: só o ack marca entregue — se você cair aqui,
// o evento desce de novo na reconexão (at-least-once).
fluxo.write({ ack: { entrega_id: ev.entrega_id } });
});
fluxo.on('error', (e) => {
// reconecte com o último entrega_id reconhecido no lugar do cursor 0
console.error('stream caiu — reconectar com cursor', e.message);
});
Antes do primeiro disparo real, prove o encanamento: o botão de teste das
configurações de confirmação manda um teste.ping — ele desce por este mesmo
stream quando o modo é grpc.
Detalhes de operação que valem saber
- Keepalive HTTP/2 de 30 s dos dois lados: conexão morta é detectada sem tráfego. Configure o seu cliente para aceitar ping sem dados.
- Os proxies intermediários são inimigos de stream longo — o nosso endpoint é direto, sem CDN na frente, com timeouts de 1 hora. Se houver proxy do SEU lado, dê a ele o mesmo respiro.
- Cada confirmação entregue por aqui fica carimbada
canal='grpc', e cada disparo aceito por aqui ficavia='grpc'— a auditoria distingue a porta, para sempre.
Quando ficar no REST
Honestidade de contrato: se você dispara campanhas em lote algumas vezes por dia, o gRPC não te dá nada — o ganho aparece no envio individual de alta frequência e no fluxo contínuo de confirmações. Integração simples merece transporte simples.