Contracts (Protobuf)
The platform's canonical message schemas — dialer.v1: CallEvent, CallDetailRecord, ComplianceDecision, and the tenant/BYOC config messages — are defined once, in Protobuf, in the public contracts repository:
https://github.com/DialerDigital/contractsIf two services on the platform talk to each other, the message they exchange is defined there — never in per-service structs. This page shows you how to consume those schemas in your own integration.
Where you will (and won't) meet these messages today
The contracts are the source of truth between platform services. The public REST API (/v1) currently speaks its own snake_case JSON (see REST API), and the WebSocket live floor has its own compact event frames. Public protojson delivery of these messages — webhooks, GET /v1/reports/cdr — is on the Roadmap. Generate clients now if you are preparing for those surfaces or consuming platform streams directly; don't expect /v1 responses to parse as protojson yet.
Repository layout
contracts/
├── proto/
│ └── dialer/
│ └── v1/
│ ├── call.proto # CallEvent + lifecycle payloads
│ ├── cdr.proto # CallDetailRecord + shared enums
│ ├── compliance.proto # ComplianceDecision + EvidenceSnapshot
│ └── tenant.proto # TenantModuleConfig, CarrierTrunk (BYOC)
├── buf.yaml # buf module config + lint rules
├── buf.gen.yaml # codegen targets (Go, Elixir, TS, Python)
└── Makefile # lint / generate / breaking / validateThe gen/ directory is git-ignored in the repo: generated code is never committed. You generate it yourself, pinned to a release tag.
Compatibility guarantees
Two complementary mechanisms protect your integration:
- The versioned package
dialer.v1. Its contents only evolve backward-compatibly: new fields and new enum values may be added; existing fields are never renamed, retyped, or renumbered. A breaking wire change requires a newdialer.v2package, withv1kept alive during a migration window. - SemVer release tags (
vX.Y.Z) on the repo:- MAJOR — a new
dialer.v(N+1)package is introduced or an entire package retired. - MINOR — backward-compatible additions (fields, messages, enum values).
- PATCH — comments, docs, tooling; zero wire-format change.
- MAJOR — a new
Compatibility is enforced mechanically: buf breaking runs in the repo's CI against main and blocks any pull request that would break the wire format.
What this means for you: pin a tag, write your consumer to tolerate unknown fields and unknown enum values, and upgrades within v1 will never break you.
Generating code
Prerequisites
- The buf CLI (the version used by the repo's CI is pinned in its workflow file).
- Only for the Elixir target: a local
protoc-gen-elixir(mix escript.install hex protobuf, with~/.mix/escriptson yourPATH). The Buf Schema Registry has no remote Elixir plugin; Go, TypeScript, and Python use version-pinned remote plugins and need nothing locally.
From a clone (pin a tag!)
git clone https://github.com/DialerDigital/contracts.git
cd contracts
git checkout v1.2.0 # always pin a release tag, never main
make generate # writes gen/go, gen/elixir, gen/ts, gen/pythonOr with buf directly:
buf generateThe configured targets (from buf.gen.yaml):
| Language | Plugin | Typical consumer |
|---|---|---|
| Go | buf.build/protocolbuffers/go (remote, pinned) | Backend services, tooling. |
| TypeScript | buf.build/bufbuild/es (remote, pinned — protobuf-es) | Web UIs, Node integrations. |
| Python | buf.build/protocolbuffers/python + pyi (remote, pinned) | Data/ETL, AI pipelines. |
| Elixir | protoc-gen-elixir (local plugin) | OTP services. |
Need another language? buf.gen.yaml is just configuration — point your own buf generate at the proto/ directory with any plugin the BSR offers.
Vendoring into your project
For most integrations the cleanest setup is a small make target in your repo:
CONTRACTS_TAG := v1.2.0
.PHONY: contracts
contracts:
rm -rf .contracts && \
git clone --depth 1 --branch $(CONTRACTS_TAG) \
https://github.com/DialerDigital/contracts.git .contracts && \
buf generate .contracts/proto --template buf.gen.local.yamlwith a buf.gen.local.yaml containing only the language target you need.
Defensive consumption
Because dialer.v1 grows over time, write consumers that survive additions:
- Tolerate unknown fields. Protobuf does this natively; if you transform to JSON, don't fail on unexpected keys.
- Tolerate unknown enum values. A new
ReasonCodeorDispositionmay appear in a MINOR release. Map unknowns to a catch-all bucket instead of crashing. - Use protojson where these messages appear as JSON. The generated code for every language includes a protojson parser that round-trips correctly (camelCase names, 64-bit ints as strings, timestamps as RFC 3339). Note: today's
/v1REST responses are not protojson — see the warning at the top of this page. - Deduplicate on identity fields.
eventId/callId/decisionId— delivery anywhere on the platform is at-least-once.
One message, every surface (target state)
When the roadmap surfaces ship, a CallDetailRecord pulled from a report endpoint, received on a webhook, or decoded from a binary stream will be the same message, parseable with the same generated bindings:
import { CallDetailRecordSchema } from "./gen/dialer/v1/cdr_pb";
import { fromJson } from "@bufbuild/protobuf";
const cdr = fromJson(CallDetailRecordSchema, webhookBody);
console.log(cdr.disposition, cdr.billsec);Staying current
- Watch the contracts repo's releases feed.
- MINOR bumps are safe to take lazily; you only need them when you want a new field.
- If a
dialer.v2ever ships, the release notes will document the migration window during which both packages are emitted.