> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ringee.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Inbound events

> Push contacts and companies from your CRM into Ringee

Send everything your CRM knows about a contact or company to a single Ringee endpoint. Ringee upserts the record, links it to your `externalId`, and makes it callable.

Pick the event you are sending in the request body above — each one has its own `data` shape.

## Reading the response

The endpoint returns **202 Accepted** for any well-formed, authenticated request. The real result is in the JSON body.

| Status                                                  | Meaning                                          | What to do                                                                       |
| ------------------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------- |
| <span className="rg-badge rg-get">processed</span>      | The event was applied.                           | Mark the sync as successful.                                                     |
| <span className="rg-badge rg-neutral">skipped</span>    | Duplicate `eventId`. Already applied earlier.    | Treat as success.                                                                |
| <span className="rg-badge rg-destructive">failed</span> | The event was accepted but could not be applied. | Show `message`, mark as a sync failure, fix and resend with a **new** `eventId`. |

<Warning>
  A `202` status code only means the transport was accepted. Do not treat it as a successful sync without checking `status` in the body.
</Warning>

## Event examples

<Tabs>
  <Tab title="contact.upserted">
    Create or update a contact from your CRM's current state.

    ```json theme={null}
    {
      "event": "contact.upserted",
      "eventId": "crm_01HX5Z7K8MZP1Q4V0G9YJ2RH3T",
      "occurredAt": "2026-05-23T14:30:00.000Z",
      "data": {
        "externalId": "ext_contact_42",
        "phoneNumber": "+14155550123",
        "firstName": "Ada",
        "lastName": "Lovelace",
        "email": "ada@example.com",
        "companyExternalId": "ext_company_7",
        "jobTitle": "Founder",
        "ownerEmail": "rep@example.com",
        "customFields": { "tier": "vip" }
      }
    }
    ```

    <Note>
      **Deduplication order:** `externalId` first, then `phoneNumber` within the same workspace. Sending `null`, `undefined` or empty strings does **not** overwrite existing values — omit a field to leave it untouched.
    </Note>
  </Tab>

  <Tab title="company.upserted">
    ```json theme={null}
    {
      "event": "company.upserted",
      "eventId": "crm_01HX5Z8FYBKD7QY5W3JN9NF2EA",
      "occurredAt": "2026-05-23T14:30:05.000Z",
      "data": {
        "externalId": "ext_company_7",
        "name": "Babbage Engines Ltd.",
        "domain": "babbage.example.com",
        "industry": "Hardware",
        "size": "11-50"
      }
    }
    ```

    <Note>
      **Deduplication order:** `externalId`, then `domain` within the same workspace.
    </Note>
  </Tab>

  <Tab title="contact.deleted">
    Marks the contact as archived in Ringee.

    ```json theme={null}
    {
      "event": "contact.deleted",
      "eventId": "crm_01HX5Z9MQ7G3T2DR0NXJW1B8VC",
      "occurredAt": "2026-05-23T14:35:00.000Z",
      "data": { "externalId": "ext_contact_42" }
    }
    ```

    <Info>
      Ringee never deletes the underlying contact. Call history, notes, callbacks and meetings are preserved — only the integration's link is archived. Re-sending `contact.upserted` with the same `externalId` un-archives it.
    </Info>
  </Tab>

  <Tab title="company.deleted">
    ```json theme={null}
    {
      "event": "company.deleted",
      "eventId": "crm_01HX5ZA1H4T6N2DR9CB7YP6F0K",
      "occurredAt": "2026-05-23T14:35:30.000Z",
      "data": { "externalId": "ext_company_7" }
    }
    ```
  </Tab>
</Tabs>

## Sending events well

<AccordionGroup>
  <Accordion title="Do not send on every render or save" icon="rotate">
    Sync only when a field Ringee cares about actually changes: name, phone, email, company, job title, owner. Sending an event on every UI update wastes quota and makes your logs unreadable.
  </Accordion>

  <Accordion title="Use E.164 phone numbers" icon="phone">
    Ringee normalizes what it can, but `+14155550123` always wins over `(415) 555-0123`. A number Ringee cannot normalize may fail dedup against an existing contact.
  </Accordion>

  <Accordion title="Never re-send what Ringee wrote" icon="arrows-rotate">
    If your CRM record was created or updated by a Ringee webhook, do not send it back. Store a `sync_origin` marker on those rows and skip them. See [Build an integration](/api/build-an-integration#prevent-sync-loops).
  </Accordion>

  <Accordion title="Queue before you send" icon="layer-group">
    Write the event to a local outbox table first, then attempt delivery. A contact save in your CRM should never fail because Ringee is momentarily unreachable.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Click-to-call" icon="phone" href="/api/click-to-call">
    Open a secure dialer for a synced contact
  </Card>

  <Card title="Outbound webhooks" icon="webhook" href="/api/outbound-webhooks">
    Receive call activity back in your CRM
  </Card>
</CardGroup>


## OpenAPI

````yaml POST /api/integrations/custom/webhook
openapi: 3.1.0
info:
  title: Ringee Public API
  version: 1.0.0
  description: >-
    The Custom Integrations API. Push contacts and companies from your CRM into
    Ringee, and open a secure dialer for a contact.


    Authenticate every request with the `cik_live_` secret key of a Custom
    Integration. The key must never reach a browser.
  contact:
    name: Ringee
    url: https://ringee.io
servers:
  - url: https://api.ringee.io
    description: Ringee Cloud
  - url: '{backendUrl}'
    description: Self-hosted — your BACKEND_URL
    variables:
      backendUrl:
        default: https://ringee.example.com
        description: The BACKEND_URL of your deployment
security:
  - ApiKeyAuth: []
paths:
  /api/integrations/custom/webhook:
    post:
      tags:
        - CRM to Ringee
      summary: Send an inbound event
      description: >-
        Upsert or archive a contact or company from your CRM.


        Returns **202 Accepted** for any well-formed, authenticated request. The
        real result is in the `status` field of the response body — always read
        it.
      operationId: receiveInboundEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/ContactUpserted'
                - $ref: '#/components/schemas/CompanyUpserted'
                - $ref: '#/components/schemas/ContactDeleted'
                - $ref: '#/components/schemas/CompanyDeleted'
              discriminator:
                propertyName: event
                mapping:
                  contact.upserted:
                    $ref: '#/components/schemas/ContactUpserted'
                  company.upserted:
                    $ref: '#/components/schemas/CompanyUpserted'
                  contact.deleted:
                    $ref: '#/components/schemas/ContactDeleted'
                  company.deleted:
                    $ref: '#/components/schemas/CompanyDeleted'
            examples:
              contact.upserted:
                summary: Create or update a contact
                value:
                  event: contact.upserted
                  eventId: crm_01HX5Z7K8MZP1Q4V0G9YJ2RH3T
                  occurredAt: '2026-05-23T14:30:00.000Z'
                  data:
                    externalId: ext_contact_42
                    phoneNumber: '+14155550123'
                    firstName: Ada
                    lastName: Lovelace
                    email: ada@example.com
                    companyExternalId: ext_company_7
                    jobTitle: Founder
                    ownerEmail: rep@example.com
                    customFields:
                      tier: vip
              company.upserted:
                summary: Create or update a company
                value:
                  event: company.upserted
                  eventId: crm_01HX5Z8FYBKD7QY5W3JN9NF2EA
                  occurredAt: '2026-05-23T14:30:05.000Z'
                  data:
                    externalId: ext_company_7
                    name: Babbage Engines Ltd.
                    domain: babbage.example.com
                    industry: Hardware
                    size: 11-50
              contact.deleted:
                summary: Archive a contact link
                value:
                  event: contact.deleted
                  eventId: crm_01HX5Z9MQ7G3T2DR0NXJW1B8VC
                  occurredAt: '2026-05-23T14:35:00.000Z'
                  data:
                    externalId: ext_contact_42
              company.deleted:
                summary: Archive a company link
                value:
                  event: company.deleted
                  eventId: crm_01HX5ZA1H4T6N2DR9CB7YP6F0K
                  occurredAt: '2026-05-23T14:35:30.000Z'
                  data:
                    externalId: ext_company_7
      responses:
        '202':
          description: >-
            Accepted. Check `status` in the body to know whether the event was
            applied.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InboundResult'
              examples:
                processed:
                  summary: Applied
                  value:
                    status: processed
                    eventId: crm_9f1c2a7e
                skipped:
                  summary: Duplicate eventId — already applied
                  value:
                    status: skipped
                    eventId: crm_9f1c2a7e
                    message: duplicate eventId
                failed:
                  summary: Accepted but could not be applied
                  value:
                    status: failed
                    eventId: crm_9f1c2a7e
                    message: phoneNumber is required
        '400':
          description: >-
            The body is not a JSON object, or a required envelope field is
            missing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or unrecognised `X-Ringee-Api-Key`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    ContactUpserted:
      type: object
      title: contact.upserted
      description: Create or update a contact from your CRM's current state.
      required:
        - event
        - eventId
        - occurredAt
        - data
      properties:
        event:
          type: string
          const: contact.upserted
          description: The event type.
        eventId:
          type: string
          description: Unique, stable id for this event. Ringee deduplicates on it.
          examples:
            - crm_01HX5Z7K8MZP1Q4V0G9YJ2RH3T
          example: crm_01HX5Z7K8MZP1Q4V0G9YJ2RH3T
        occurredAt:
          type: string
          format: date-time
          description: When the change happened in your system.
          examples:
            - '2026-05-23T14:30:00.000Z'
          example: '2026-05-23T14:30:00.000Z'
        data:
          type: object
          required:
            - externalId
            - phoneNumber
          description: >-
            Omit a field to leave it untouched. Sending `null`, `undefined` or
            an empty string does not overwrite an existing value.
          properties:
            externalId:
              type: string
              description: >-
                Stable id of the contact in your system. This is the join key
                for every later event.
              examples:
                - ext_contact_42
              example: ext_contact_42
            phoneNumber:
              type: string
              description: >-
                Phone in any format; normalized to E.164 when possible. Required
                because Ringee must be able to dial the contact.
              examples:
                - '+14155550123'
              example: '+14155550123'
            firstName:
              type: string
              description: Given name.
              examples:
                - Ada
              example: Ada
            lastName:
              type: string
              description: Family name.
              examples:
                - Lovelace
              example: Lovelace
            fullName:
              type: string
              description: Pre-composed full name.
            name:
              type: string
              description: Display name.
            email:
              type: string
              description: Primary email.
              examples:
                - ada@example.com
              example: ada@example.com
            companyExternalId:
              type: string
              description: External company id to link this contact to.
              examples:
                - ext_company_7
              example: ext_company_7
            companyName:
              type: string
              description: Fallback company name when no `companyExternalId` is provided.
            jobTitle:
              type: string
              description: Position or title.
              examples:
                - Founder
              example: Founder
            ownerExternalId:
              type: string
              description: External owner id (sales rep).
            ownerEmail:
              type: string
              description: >-
                Owner email — resolves to a Ringee user when the integration is
                organization-scoped.
              examples:
                - rep@example.com
              example: rep@example.com
            source:
              type: string
              description: Lead source label.
            customFields:
              type: object
              additionalProperties: true
              description: Arbitrary key/value pairs stored on the contact.
            crmMetadata:
              type: object
              additionalProperties: true
              description: Provider-specific metadata stored on the contact.
    CompanyUpserted:
      type: object
      title: company.upserted
      description: Create or update a company. Deduplicated on `externalId`, then `domain`.
      required:
        - event
        - eventId
        - occurredAt
        - data
      properties:
        event:
          type: string
          const: company.upserted
          description: The event type.
        eventId:
          type: string
          description: Unique, stable id for this event.
          examples:
            - crm_01HX5Z8FYBKD7QY5W3JN9NF2EA
          example: crm_01HX5Z8FYBKD7QY5W3JN9NF2EA
        occurredAt:
          type: string
          format: date-time
          examples:
            - '2026-05-23T14:30:05.000Z'
          example: '2026-05-23T14:30:05.000Z'
        data:
          type: object
          required:
            - externalId
            - name
          properties:
            externalId:
              type: string
              description: Stable id of the company in your system.
              examples:
                - ext_company_7
              example: ext_company_7
            name:
              type: string
              description: Display name.
              examples:
                - Babbage Engines Ltd.
              example: Babbage Engines Ltd.
            legalName:
              type: string
              description: Registered legal name.
            domain:
              type: string
              description: Primary web domain — used as a secondary dedup signal.
              examples:
                - babbage.example.com
              example: babbage.example.com
            industry:
              type: string
              description: Industry label.
              examples:
                - Hardware
              example: Hardware
            size:
              type: string
              description: Headcount band.
              examples:
                - 11-50
              example: 11-50
            phone:
              type: string
              description: Main phone.
            website:
              type: string
              description: Homepage URL.
            source:
              type: string
              description: Source label.
            customFields:
              type: object
              additionalProperties: true
              description: Arbitrary key/value pairs.
            crmMetadata:
              type: object
              additionalProperties: true
              description: Provider-specific metadata.
    ContactDeleted:
      type: object
      title: contact.deleted
      description: >-
        Archives the integration's link to the contact. Ringee never deletes the
        underlying contact — call history, notes, callbacks and meetings are
        preserved.
      required:
        - event
        - eventId
        - occurredAt
        - data
      properties:
        event:
          type: string
          const: contact.deleted
          description: The event type.
        eventId:
          type: string
          examples:
            - crm_01HX5Z9MQ7G3T2DR0NXJW1B8VC
          example: crm_01HX5Z9MQ7G3T2DR0NXJW1B8VC
        occurredAt:
          type: string
          format: date-time
          examples:
            - '2026-05-23T14:35:00.000Z'
          example: '2026-05-23T14:35:00.000Z'
        data:
          type: object
          required:
            - externalId
          properties:
            externalId:
              type: string
              description: >-
                The external contact id you previously sent in
                `contact.upserted`.
              examples:
                - ext_contact_42
              example: ext_contact_42
    CompanyDeleted:
      type: object
      title: company.deleted
      description: Archives the integration's link to the company.
      required:
        - event
        - eventId
        - occurredAt
        - data
      properties:
        event:
          type: string
          const: company.deleted
          description: The event type.
        eventId:
          type: string
          examples:
            - crm_01HX5ZA1H4T6N2DR9CB7YP6F0K
          example: crm_01HX5ZA1H4T6N2DR9CB7YP6F0K
        occurredAt:
          type: string
          format: date-time
          examples:
            - '2026-05-23T14:35:30.000Z'
          example: '2026-05-23T14:35:30.000Z'
        data:
          type: object
          required:
            - externalId
          properties:
            externalId:
              type: string
              description: >-
                The external company id you previously sent in
                `company.upserted`.
              examples:
                - ext_company_7
              example: ext_company_7
    InboundResult:
      type: object
      required:
        - status
        - eventId
      properties:
        status:
          type: string
          enum:
            - processed
            - skipped
            - failed
          description: >-
            `processed` — applied. `skipped` — duplicate `eventId`, already
            applied earlier; treat as success. `failed` — accepted but could not
            be applied; fix and resend with a **new** `eventId`.
        eventId:
          type: string
          description: Echoes the `eventId` you sent.
        message:
          type: string
          description: Present on `skipped` and `failed`. Surface it in your sync logs.
    Error:
      type: object
      properties:
        statusCode:
          type: integer
          examples:
            - 400
          example: 400
        message:
          type: string
          description: What went wrong.
          examples:
            - >-
              Must provide either contactExternalId (linked to a contact) or
              phoneNumber
          example: >-
            Must provide either contactExternalId (linked to a contact) or
            phoneNumber
        error:
          type: string
          examples:
            - Bad Request
          example: Bad Request
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Ringee-Api-Key
      description: >-
        The `cik_live_` secret key of a Custom Integration. Shown once at
        creation. Server-side only.

````