Build with Pongg
Custom CRM bridge
Keep your CRM. Let Pongg send caller details, capture leads and request or confirm appointments through a bridge your developers control.
Contract pongg.crm.v1 · Updated October 19, 2026
Download OpenAPI 3.1 JSON · Open Integrations in Pongg
Quick start
Your developer builds a small, customer-owned HTTPS service that translates this contract into your CRM's approved API. Enter that service's base URL in Pongg → Integrations → Custom CRM. A vendor's generic API URL will not work by itself.
- Implement
contacts.upsertand at least one ofleads.createorappointments.create. - Implement the read-only
capabilitiesresponse below. Add search and real availability only when your bridge supports them. - Choose REST or MCP, then an API key or OAuth service account in Pongg. Bind the credential to the client tenant.
- Connect to run the capabilities check. With your Success Team, send a clearly labelled test lead and test a booking before enabling live delivery.
The connection check reads capabilities only; it does not create a contact, lead or appointment. Demo and sandbox accounts cannot connect this bridge to a real CRM. This guide describes a connector to your service, not a public Pongg API, OAuth app marketplace or general-purpose MCP agent.
Data Pongg sends
These tables describe the actual serialized fields. A field marked Present is included even when its string is empty or its array has no items. Optional fields are omitted, not sent as null. Booking start and end are always non-null offset-aware timestamps. Accept additive fields so compatible updates do not break your bridge.
There are no dedicated fields for full transcripts, recording URLs, payment or card data, or arbitrary metadata in this bridge contract. Summary and notes are free-form call information, so handle them according to your own data controls.
Contact
| Field | Type | Presence | Meaning |
|---|---|---|---|
full_name | string | Present | Caller name as captured; empty when unknown. |
first_name | string | Present | Caller given name when available; may be empty. |
last_name | string | Present | Caller family name when available; may be empty. |
address | string | Present | Service or street address captured from the caller; may be empty. |
source | string | Present | Lead attribution text, for example Pongg (phone); may be empty. |
call_id | string | Present | Originating call identifier. May be empty during an in-call action before post-call processing. |
phones | array<string> | Present | Array of callback phone strings. Normalized where possible; can be empty or contain ambiguous bare-digit identities. |
emails | array<string> | Present | Array of email strings captured from the caller. Can be empty; no assumed lowercase or strict format. |
tags | array<string> | Present | Array of source tag strings, for example Pongg. Can be empty. |
postal_code | string | Optional, omitted when absent | Optional captured postal code. Omitted when no postal code is supplied. |
Phones are normalized where possible. Ambiguous numbers can remain bare digits. Emails are sent as captured; do not assume a strict email format or lowercase value. Contact upsert needs at least one usable phone or email and must match an existing identity before creating a duplicate.
Lead
| Field | Type | Presence | Meaning |
|---|---|---|---|
job_type | string | Present | Requested service or job type captured from the call; free-form string, not a fixed enum. May be empty. |
summary | string | Present | Short description of the caller need or call result; may be empty. |
notes | string | Present | Additional free-form call details and instructions; may be empty. |
urgency | string | Present | Captured urgency text or label. No fixed enum is required; may be empty. |
source | string | Present | Lead attribution text, for example Pongg (phone); may be empty. |
call_id | string | Present | Originating call identifier for provenance and call-based idempotency; may be empty. |
contact | Contact | Present | Nested caller Contact object with the fields listed above. |
location | Location | Optional, omitted when absent | Optional configured business location: id and name. Omitted when the call has no location. |
Appointment
| Field | Type | Presence | Meaning |
|---|---|---|---|
contact_external_id | string | Present | Durable contact ID previously returned by this bridge. |
timezone | string | Present | Valid IANA timezone, for example America/Chicago. |
start | string | Present | Offset-aware ISO 8601 instant. Required and non-null for booking. |
end | string | Present | Offset-aware ISO 8601 instant. Required and non-null for booking. |
slot_text | string | Present | Verbatim or display time description from the call. It is not parsed as a substitute for start and end; may be empty. |
job_type | string | Present | Requested service or job type as captured; free-form string, not a fixed enum. May be empty. |
address | string | Present | Service address for the visit as captured; may be empty. |
notes | string | Present | Free-form booking details or instructions; may be empty. |
title | string | Present | Appointment title or display label; may be empty. |
source | string | Present | Appointment attribution text, for example Pongg (phone); may be empty. |
location | Location | Optional, omitted when absent | Optional configured business location: id and name. Omitted when the call has no location. |
service_id | string | Optional, omitted when absent | Optional service identifier supplied with the booking. Omitted when empty. |
The appointment object does not include call_id, status or caller_confirmed. A location, when present, contains only id and name. The contact itself has no location object.
Location
| Field | Type | Presence | Meaning |
|---|---|---|---|
id | string | Present | Configured location ID. |
name | string | Present | Configured location name; may be empty. |
Authentication
API key: Pongg sends the configured key as a Bearer credential on every bridge request. Keys must contain visible ASCII characters without spaces or control characters. Store credentials in the Custom CRM connection form, never in the endpoint URL.
Authorization: Bearer YOUR_BRIDGE_API_KEYOAuth 2.0 client credentials: provide your HTTPS token URL, client ID, client secret and optional scope. The scope field is sent only when provided. Pongg sends a form POST using HTTP Basic client authentication. Client ID and secret are form-encoded before Basic encoding. This is a machine account flow, not an end-user browser consent screen.
POST https://auth.example.com/oauth/token
Authorization: Basic BASE64_ENCODED_CLIENT_CREDENTIALS
Content-Type: application/x-www-form-urlencoded
Accept: application/json
grant_type=client_credentials&scope=crm.read+crm.write{
"access_token": "EXAMPLE_ACCESS_TOKEN",
"token_type": "Bearer",
"expires_in": 3600
}Return token_type: "Bearer". expires_in is optional; only a numeric lifetime greater than 30 seconds is cached, for at most five minutes with a 30-second safety margin. Without a usable lifetime, Pongg requests a fresh grant next time. The cache is bound to tenant, account, endpoint, configuration and credentials. A bridge HTTP 401 causes one fresh grant and one replay of the same request; another 401 stops. API-key 401s are not replayed through OAuth.
REST operations
Every operation is an HTTPS POST. Pongg appends the path to your configured base URL. For a base of https://crm.example.com/pongg/v1, capabilities is https://crm.example.com/pongg/v1/capabilities. All examples below are synthetic.
Every body contains tenant_id and contract_version. All three write operations also carry the same opaque 64-character hexadecimal key in the Idempotency-Key header and idempotency_key body field. Reads do not carry a write key.
POST /capabilities
Return the exact contract version and only the supported operation names. Include contacts.upsert and at least one of leads.create or appointments.create. If you advertise availability.list, also implement appointments.create and return booking_closes_availability:true for the same calendar.
Request JSON
{
"tenant_id": "tenant-example-001",
"contract_version": "pongg.crm.v1"
}Response JSON
{
"contract_version": "pongg.crm.v1",
"operations": [
"contacts.search",
"contacts.upsert",
"leads.create",
"availability.list",
"appointments.create"
],
"booking_closes_availability": true
}POST /contacts/search
Both search fields are strings and may be empty. Return contact:null when there is no match, or a nonblank durable contact ID. Do not invent a match.
Request JSON
{
"tenant_id": "tenant-example-001",
"contract_version": "pongg.crm.v1",
"phone": "+12145550148",
"email": "jordan@example.com"
}Response JSON
{
"contact": {
"id": "contact-example-001"
}
}{
"contact": null
}
POST /contacts/upsert
Match phone or email identity before creating a contact. Return a durable nonblank ID and a real boolean is_new. String values such as "false" do not satisfy the response.
Request JSON
{
"tenant_id": "tenant-example-001",
"contract_version": "pongg.crm.v1",
"contact": {
"full_name": "Jordan Example",
"first_name": "Jordan",
"last_name": "Example",
"phones": [
"+12145550148"
],
"emails": [
"jordan@example.com"
],
"address": "100 Example St, Dallas TX 75201",
"source": "Pongg (phone)",
"tags": [
"Pongg"
],
"call_id": "example-call-001",
"postal_code": "75201"
},
"idempotency_key": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}Response JSON
{
"id": "contact-example-001",
"is_new": false
}POST /availability
The window includes the requested interval, IANA zone, weekly windows, duration and job type. Weekly windows are [day, openingMinute, closingMinute], with 0=Monday; an empty array means not supplied. slot_minutes can be 0 when unspecified. Return at most 50 real openings inside the interval, with end after start. Pongg sorts and deduplicates slots by instant. Service IDs or arrival-window metadata in a slot response are not consumed by this bridge.
Request JSON
{
"tenant_id": "tenant-example-001",
"contract_version": "pongg.crm.v1",
"window": {
"start": "2026-10-20T00:00:00+00:00",
"end": "2026-10-21T00:00:00+00:00",
"timezone": "America/Chicago",
"windows": [
[
1,
420,
1080
]
],
"slot_minutes": 60,
"job_type": "Roof repair"
}
}Response JSON
{
"slots": [
{
"start": "2026-10-20T15:00:00+00:00",
"end": "2026-10-20T16:00:00+00:00",
"timezone": "America/Chicago"
}
]
}POST /appointments
Check capacity and create the appointment atomically. Return a durable ID, confirmed:true, exact requested start/end instants and a valid IANA timezone. An unconfirmed request, shifted time, empty ID or missing timestamp is a failed booking. Return HTTP 409 if the slot is taken.
Request JSON
{
"tenant_id": "tenant-example-001",
"contract_version": "pongg.crm.v1",
"appointment": {
"contact_external_id": "contact-example-001",
"timezone": "America/Chicago",
"start": "2026-10-20T15:00:00+00:00",
"end": "2026-10-20T16:00:00+00:00",
"slot_text": "Tuesday at 10 AM Central",
"job_type": "Roof repair",
"address": "100 Example St, Dallas TX 75201",
"notes": "Synthetic example: inspect a small roof leak.",
"title": "Roof repair visit",
"source": "Pongg (phone)",
"location": {
"id": "dallas",
"name": "Dallas office"
},
"service_id": "repair-inspection"
},
"idempotency_key": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}Response JSON
{
"id": "appointment-example-001",
"confirmed": true,
"start": "2026-10-20T15:00:00+00:00",
"end": "2026-10-20T16:00:00+00:00",
"timezone": "America/Chicago"
}POST /leads
Create or return the existing lead for the same idempotency key. Return its durable nonblank ID. A generic ok:true acknowledgement is not a receipt.
Request JSON
{
"tenant_id": "tenant-example-001",
"contract_version": "pongg.crm.v1",
"lead": {
"contact": {
"full_name": "Jordan Example",
"first_name": "Jordan",
"last_name": "Example",
"phones": [
"+12145550148"
],
"emails": [
"jordan@example.com"
],
"address": "100 Example St, Dallas TX 75201",
"source": "Pongg (phone)",
"tags": [
"Pongg"
],
"call_id": "example-call-001",
"postal_code": "75201"
},
"job_type": "Roof repair",
"summary": "Caller requested an inspection.",
"notes": "Synthetic example only.",
"urgency": "routine",
"source": "Pongg (phone)",
"call_id": "example-call-001",
"location": {
"id": "dallas",
"name": "Dallas office"
}
},
"idempotency_key": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}Response JSON
{
"id": "lead-example-001"
}MCP transport
Choose MCP to use the fixed Pongg tools over Streamable HTTP 2026-07-28. Enter the MCP endpoint URL itself; no REST suffix is appended. Arguments and structured responses match the REST contract.
| Operation | Fixed tool |
|---|---|
| capabilities | pongg_capabilities |
| contacts.search | pongg_contacts_search |
| contacts.upsert | pongg_contacts_upsert |
| availability.list | pongg_availability_list |
| appointments.create | pongg_appointments_create |
| leads.create | pongg_leads_create |
Each POST carries the following headers. Write calls also include Idempotency-Key. The protocol, method and tool-name headers must match the JSON body.
Content-Type: application/json
Accept: application/json, text/event-stream
Authorization: Bearer YOUR_BRIDGE_CREDENTIAL
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: pongg_capabilities
{
"jsonrpc": "2.0",
"id": "0123456789abcdef01234567",
"method": "tools/call",
"params": {
"name": "pongg_capabilities",
"arguments": {
"tenant_id": "tenant-example-001",
"contract_version": "pongg.crm.v1"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "Pongg CRM bridge",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}{
"jsonrpc": "2.0",
"id": "0123456789abcdef01234567",
"result": {
"resultType": "complete",
"isError": false,
"structuredContent": {
"contract_version": "pongg.crm.v1",
"operations": [
"contacts.search",
"contacts.upsert",
"leads.create",
"availability.list",
"appointments.create"
],
"booking_closes_availability": true
}
}
}Echo the 24-character JSON-RPC request ID; it correlates the response and is separate from the 64-character write idempotency key. Return resultType: "complete" and the matching REST response object in structuredContent. Return isError: false on success. Plain text is not parsed into a receipt. JSON and bounded request-scoped SSE data frames are supported; notifications without request IDs may precede the final response.
No initialize/session exchange, arbitrary tool discovery, resources, prompts, sampling, elicitation, browser sign-in or input-required conversations are supported. Server requests, JSON-RPC errors and incomplete results fail the operation. Remote prose and tool descriptions never become instructions for the agent.
Receipts, time and retries
Use explicit UTC offsets in every timestamp and an IANA timezone such as America/Chicago. Appointment input is checked before sending; a naive or nonpositive interval is refused. A receipt must match both requested instants exactly. A prior availability read does not reserve capacity.
Persist a unique idempotency-key-to-receipt mapping in the same CRM transaction as each write. Replaying a write must return the original record. Contact keys are based on normalized identity; appointment keys use tenant, contact, UTC interval, service or job type and location; lead keys use call ID and job type when available. Mutable descriptions do not change those appointment or call-based lead keys. Treat keys as opaque.
| Result | What Pongg does |
|---|---|
2xx | Parses the required JSON object or MCP structured receipt. An empty response or acknowledgement alone is not success. |
409 | Treats the operation as a conflict or unavailable slot. No booking is confirmed and it is not automatically retried. |
429 or 5xx | Post-call delivery can retry under the worker policy with the same idempotency key. Retry-After is bounded to five minutes. |
| Network failure | Can retry in post-call delivery. A call-time operation returns a structured failure or fallback rather than promising an unconfirmed booking. |
401 | OAuth service accounts receive one fresh grant/replay. Further 401s, and API-key 401s, require corrected access. |
Other 4xx | Requires corrected configuration or data. Remote error bodies are not displayed verbatim or treated as successful receipts. |
Only the capabilities probe is part of connecting. The bridge must actually honor identity matching, idempotency, permissions and atomic slot closure. Test those behaviors with your team before relying on live calls.
Security and limits
- Use public HTTPS with a verifiable certificate. Every DNS answer must be public; the selected address is pinned while TLS verifies the original hostname.
- Redirects, proxies, URL credentials, query strings and fragments are not supported. Use a clean endpoint path.
- Each HTTP exchange has a 10-second total deadline. Request and response bodies are each limited to 1 MiB. Return uncompressed JSON or uncompressed MCP SSE.
- Bind each credential to its tenant. Reject a tenant mismatch even if the incoming phone number or CRM record looks valid.
- Store and rotate credentials securely. Pongg uses its tenant-bound encrypted integration store; never paste production secrets into examples, URLs or support messages.
- Keep caller data limited to the workflow your company authorized. Free-form notes and summaries need the same access controls as CRM records.
Launch checklist
- Confirm your vendor permits the required API access and that your account has write permissions.
- Connect the customer-owned bridge and verify the read-only capabilities result.
- Send a labelled setup lead. Confirm the mapped fields and durable CRM ID with your Success Team.
- Repeat the same idempotency key and confirm it returns one record.
- Test empty availability, an occupied slot, a successful booking and the same booking replay. Verify the booked slot disappears from the same availability source.
- Test the client's local timezone, overnight intervals and daylight saving changes.
- Test rejected access, rate limits and a timeout after a successful write. Confirm retries reuse the original receipt.
Versioning: all requests declare pongg.crm.v1. Accept compatible additive fields. A breaking contract requires a separately coordinated version; do not silently change field meaning or return a different major contract.
Your Success Team can help confirm the intended lead, appointment and calendar behavior. Start in Pongg Integrations.
