https://github.com/localytics/push-notification-protos
Protocol Buffer definitions for the push notification service.
https://github.com/localytics/push-notification-protos
Last synced: about 1 month ago
JSON representation
Protocol Buffer definitions for the push notification service.
- Host: GitHub
- URL: https://github.com/localytics/push-notification-protos
- Owner: localytics
- License: mit
- Created: 2026-05-11T15:16:21.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-05-11T17:35:32.000Z (about 2 months ago)
- Last Synced: 2026-05-11T18:33:13.701Z (about 2 months ago)
- Size: 13.7 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# push-notification-protos
Protocol Buffer definitions for the Localytics push notification service.
This repository contains a single source-of-truth schema, [`push.proto`](push.proto), that defines a bidirectional streaming gRPC API for sending push notifications to iOS, Android, and Web clients. The long-form client guide — with full runnable examples in Go, Python, Node.js, Java, and `grpcurl`, plus the full table of gRPC status codes — lives in [`usage.md`](usage.md). This README is the entry point: it explains what is in the proto, how the protocol works, and how to wire the generated stubs into a codebase.
## Table of contents
- [What `push.proto` defines](#what-pushproto-defines)
- [Message reference](#message-reference)
- [The streaming protocol](#the-streaming-protocol)
- [Authentication and endpoint](#authentication-and-endpoint)
- [Limits worth knowing up front](#limits-worth-knowing-up-front)
- [Integrating into your codebase](#integrating-into-your-codebase)
- [1. Vendor the proto](#1-vendor-the-proto)
- [2. Generate stubs](#2-generate-stubs)
- [3. Wire up a client](#3-wire-up-a-client)
- [Versioning and compatibility](#versioning-and-compatibility)
- [Repo layout](#repo-layout)
## What `push.proto` defines
The file is plain `proto3`. It declares:
- The package `push` (so generated symbols live under `push.*`, `push_pb2`, etc.).
- A Go import path: `option go_package = "github.com/localytics/push-notification-protos/push"`. Generated Go code will live under this import path.
- A single external import, `google/protobuf/struct.proto`, which ships with `protoc` and the language plugins. It is only used to type the free-form `extra` fields on the platform params messages.
- One gRPC service, `PushService`, with one RPC, `StreamPush`, which is **bidirectional streaming**.
- A set of request and response messages used to drive that stream.
There are no enums and no nested messages; every type is top-level. Field numbers are stable — only additive changes should be expected.
## Message reference
The proto groups messages into a few small layers. Every field listed here comes from `push.proto` directly.
### `Alert` — the notification content
The user-visible part of the push.
- `body` (`string`, required) — the notification text.
- `title` (`string`, optional) — short title.
- `subtitle` (`string`, optional) — iOS 10+ only; the server rejects `subtitle` if `title` is not also set.
### Per-platform parameter messages
Each is optional. Set the ones you need; omitted platforms are simply not delivered to.
**`IOSParams`** — iOS-specific options:
- `sound` (`string`) — sound file name, or `"default"`.
- `badge` (`int32`) — unread badge number.
- `category` (`string`) — interactive push category.
- `content_available` (`bool`) — defaults to `true`.
- `mutable_content` (`bool`) — defaults to `false`.
- `extra` (`google.protobuf.Struct`) — arbitrary JSON-shaped payload merged into the APNs message.
**`AndroidParams`** — Android-specific options:
- `priority` (`string`) — message priority, typically `"normal"` or `"high"`.
- `extra` (`google.protobuf.Struct`) — arbitrary JSON-shaped payload.
**`WebParams`** — Web push options:
- `badge` (`string`) — URL to a badge image.
- `dir` (`string`) — `"ltr"`, `"rtl"`, or `"auto"`.
- `icon` (`string`) — URL to an icon image.
- `require_interaction` (`bool`)
- `renotify` (`bool`)
- `silent` (`bool`)
- `tag` (`string`)
- `extra` (`google.protobuf.Struct`)
The `extra` fields are typed as `google.protobuf.Struct`, which represents an arbitrary JSON object (nested objects and arrays allowed, not just flat string maps). Every language has a builder for it: `structpb.NewStruct(...)` in Go, `struct_pb2.Struct().update({...})` in Python, `Struct.newBuilder().putFields(...)` in Java, and a plain JS object in Node.js.
### `Labels` — up to 10 tracking labels
A flat container with fields `label1` through `label10`, all `string` and all optional. Used for campaign performance tracking; the meaning of each slot is yours to decide.
### `StreamInit` — the first message on the stream
Sent once, as the first frame, to identify the stream.
- `app_id` (`string`, required) — the application UUID.
- `request_id` (`string`, optional, ≤ 255 chars) — auto-generated by the server if omitted.
- `campaign_key` (`string`, optional, ≤ 255 chars, must match `^[\w\-.]+$`).
- `labels` (`Labels`, optional).
- `all_devices` (`bool`, optional) — defaults to `false`.
- `test` (`bool`, optional) — test-mode flag; messages are validated but not delivered.
### `PushRequest` — one per batch of recipients
Sent zero or more times after `StreamInit`. Each `PushRequest` targets one or more `customer_ids` with the same alert and platform params.
- `customer_ids` (`repeated string`, required, non-empty) — up to 30,000 per request.
- `alert` (`Alert`, required) — rejected if missing.
- `ios` (`IOSParams`, optional).
- `android` (`AndroidParams`, optional).
- `web` (`WebParams`, optional).
### `PushStreamMessage` — client-to-server envelope
A `oneof` wrapper so the stream can carry either kind of frame:
```protobuf
message PushStreamMessage {
oneof payload {
StreamInit init = 1;
PushRequest push = 2;
}
}
```
The first frame must populate `init`; every subsequent frame must populate `push`. This is enforced by the server, not by the proto.
### `PushFailure` — partial failure (server → client)
Streamed back zero or more times as failures are discovered. The stream stays open; it does **not** terminate the call.
- `customer_ids` (`repeated string`) — the subset of IDs that failed (from a `PushRequest` you sent).
- `reason` (`string`) — human-readable failure description.
### `PushResponse` — final summary (server → client)
Sent exactly once, always last, just before the server closes the stream.
- `request_id` (`string`) — matches the stream's request ID (the one you sent in `StreamInit`, or the one the server generated).
- `total_messages` (`int32`) — number of `PushRequest` messages processed.
- `total_customer_ids` (`int32`) — total IDs across all messages.
- `status` (`string`) — `"accepted"` or `"error"`.
- `error` (`string`, optional) — set only when `status == "error"`.
- `campaign_id` (`int64`) — server-assigned campaign ID.
### `PushStreamResponse` — server-to-client envelope
Mirrors `PushStreamMessage` on the server side:
```protobuf
message PushStreamResponse {
oneof response {
PushFailure failure = 1;
PushResponse summary = 2;
}
}
```
### `PushService` — the RPC
```protobuf
service PushService {
rpc StreamPush(stream PushStreamMessage) returns (stream PushStreamResponse);
}
```
One method, both sides streaming. There are no other RPCs.
## The streaming protocol
The proto defines the *shape* of the messages; the *order* in which they're allowed is part of the contract. Every client must do this:
```
client → StreamInit (must be first; exactly one)
client → PushRequest (zero or more; in any order)
client → PushRequest
client → ... CloseSend / half-close ... (signal you're done sending)
server → PushFailure (zero or more, as failures happen)
server → PushFailure
server → PushResponse summary (exactly one; always last)
server → EOF (server closes the stream)
```
Concretely:
1. Open the bidirectional stream by calling `StreamPush` on the generated client.
2. Send a single `PushStreamMessage` with the `init` arm populated. The server rejects any other frame in this slot.
3. Send as many `PushStreamMessage`s with the `push` arm populated as you need, batching `customer_ids` per request to stay efficient.
4. Half-close the send side (`CloseSend()` in Go, `stream.end()` in Node, `done_writing()` in Python aio, `requestObserver.onCompleted()` in Java). Until you do this, the server will not emit its summary.
5. Read every response until EOF. Treat `PushFailure` frames as partial-success information — they do **not** end the stream. The `PushResponse` summary is always the last message before EOF.
A common bug is to stop reading after the first `PushFailure`. Don't — the summary still has to come through.
## Authentication and endpoint
The service is reachable on the sandbox at:
```
trans-api-grpc.sandbox53.localytics.com:50051
```
Authentication is HTTP Basic, carried in gRPC metadata on the call (not in the proto itself):
```
authorization: Basic
```
The server authenticates the call, then verifies that `StreamInit.app_id` belongs to the authenticated organization. See [`usage.md`](usage.md) for the full mapping from auth failures to gRPC status codes.
## Limits worth knowing up front
These limits are enforced by the server, not encoded in the proto, but they shape how you should batch:
- **10,000** `PushRequest` messages per stream.
- **30,000** `customer_ids` per `PushRequest`.
- **10 minutes** of wall-clock per stream.
- **4 MiB** maximum message size.
- `request_id` and `campaign_key` ≤ **255 chars**; `campaign_key` must match `^[\w\-.]+$`.
If you have more recipients than fit in one stream, open multiple streams in parallel — each gets its own `request_id` and limits independently.
## Integrating into your codebase
The mechanics are the same in every language: vendor the proto, generate stubs, then write a small client that drives the protocol described above.
### 1. Vendor the proto
You have three reasonable options:
- **Git submodule.** `git submodule add https://.../push-notification-protos third_party/push-notification-protos` and point your build at `third_party/push-notification-protos/push.proto`. Easiest way to track upstream and stay pinned to a specific commit.
- **Pin a tag and copy.** Copy `push.proto` (and `LICENSE`) into your repo under e.g. `proto/push.proto`. Record the upstream commit hash somewhere in your repo so you can re-sync intentionally.
- **`buf` modules.** If you use [Buf](https://buf.build/), you can publish this proto as a module and depend on it from your `buf.yaml`.
`push.proto` only imports `google/protobuf/struct.proto`. That import ships with `protoc` itself (under the `include/` directory of the protobuf release) and with each language's protobuf runtime, so you do **not** need to vendor it separately.
### 2. Generate stubs
Use `protoc` (or `buf generate`). Example invocations are below; replace `proto/push.proto` with wherever you placed the file.
**Go.** The generated package import path follows the `option go_package` in the proto, i.e. `github.com/localytics/push-notification-protos/push`.
```bash
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
protoc \
--go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/push.proto
go get google.golang.org/grpc google.golang.org/protobuf
```
This produces `push.pb.go` (message types) and `push_grpc.pb.go` (the `PushServiceClient` interface and `NewPushServiceClient` constructor).
**Python.** Use `grpcio-tools` so the gRPC stubs are generated alongside the message types.
```bash
pip install grpcio grpcio-tools protobuf
python -m grpc_tools.protoc \
-I proto \
--python_out=./gen \
--grpc_python_out=./gen \
proto/push.proto
```
This produces `push_pb2.py` (messages) and `push_pb2_grpc.py` (the `PushServiceStub`).
**Node.js / TypeScript.** Two common approaches:
- *Dynamic loading*, no codegen step: `npm install @grpc/grpc-js @grpc/proto-loader`, then `protoLoader.loadSync('push.proto', ...)` at runtime. Simplest to get started; no TypeScript types out of the box.
- *Static codegen*: `npm install -D grpc-tools grpc_tools_node_protoc_ts`, run `grpc_tools_node_protoc` against `push.proto`. You get `.js` + `.d.ts` and full type checking.
**Java.** Either invoke `protoc` directly:
```bash
protoc --java_out=src/main/java --grpc-java_out=src/main/java proto/push.proto
```
…or, more commonly, use the `com.google.protobuf` Gradle/Maven plugin pointed at your proto directory. Add these runtime dependencies:
```groovy
implementation 'io.grpc:grpc-netty-shaded:1.68.0'
implementation 'io.grpc:grpc-protobuf:1.68.0'
implementation 'io.grpc:grpc-stub:1.68.0'
implementation 'com.google.protobuf:protobuf-java:4.29.0'
```
The generated classes live under the `push` package (from the proto's `package push;` declaration).
**Other languages.** `push.proto` is plain proto3 with one well-known import, so it works unchanged with `protoc` plugins for C#, Ruby, Kotlin, Swift, and Rust (`tonic`). Generate stubs the same way and follow the protocol described above.
### 3. Wire up a client
The same five steps in every language:
1. **Open a channel/connection** to the endpoint. Reuse one channel across the application — gRPC multiplexes streams over a single HTTP/2 connection, so you don't need a new channel per call.
2. **Attach auth metadata.** Build the `Basic` header from `api_key:api_secret`, base64-encode it, and attach it to the call (per-RPC metadata is fine; you don't have to use an interceptor).
3. **Set a deadline.** The server enforces a 10-minute cap; set your client deadline slightly above that (e.g. 11 minutes) as a safety net.
4. **Drive the stream.** Send one `PushStreamMessage{ init: StreamInit{...} }`, then any number of `PushStreamMessage{ push: PushRequest{...} }`, then half-close.
5. **Drain responses until EOF.** Switch on the `response` oneof: log/collect `PushFailure`s, store the final `PushResponse` summary, and break on EOF (or `onCompleted`, depending on language).
Self-contained, minimal example clients live under [`examples/`](examples):
- [`examples/go/`](examples/go) — Go + `protoc`, driven by a `Makefile`.
- [`examples/java/`](examples/java) — Java + Gradle with the `protobuf-gradle-plugin`.
- [`examples/rust/`](examples/rust) — Rust + `tonic` (codegen via `build.rs`).
All three are roughly 100 lines each and exercise the full flow: open the stream, send `StreamInit` + one `PushRequest`, half-close, drain failures, print the summary.
Full client examples for Python (sync and async), Node.js, and `grpcurl`, plus the complete gRPC status-code table, retry strategy, and the `google.protobuf.Struct` builders for the `extra` fields in each language, live in [`usage.md`](usage.md).
## Versioning and compatibility
Treat `push.proto` as a public contract:
- Field numbers are stable. Don't reuse or renumber them locally.
- Only additive changes (new optional fields, new `oneof` arms with new tags) should be expected on existing messages. Wire compatibility goes both directions for these.
- Pin to a specific git tag or commit when vendoring, so a regen never silently picks up an upstream change.
## Repo layout
```
push.proto # the schema — this is what you generate stubs from
usage.md # full client integration guide (auth, limits, runnable examples)
examples/ # minimal self-contained clients (Go, Java, Rust)
LICENSE
```