Skip to content

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/contracts

If 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 / validate

The 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:

  1. 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 new dialer.v2 package, with v1 kept alive during a migration window.
  2. 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.

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/escripts on your PATH). 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!)

bash
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/python

Or with buf directly:

bash
buf generate

The configured targets (from buf.gen.yaml):

LanguagePluginTypical consumer
Gobuf.build/protocolbuffers/go (remote, pinned)Backend services, tooling.
TypeScriptbuf.build/bufbuild/es (remote, pinned — protobuf-es)Web UIs, Node integrations.
Pythonbuf.build/protocolbuffers/python + pyi (remote, pinned)Data/ETL, AI pipelines.
Elixirprotoc-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:

makefile
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.yaml

with 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 ReasonCode or Disposition may 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 /v1 REST 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:

ts
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.v2 ever ships, the release notes will document the migration window during which both packages are emitted.

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.