GoTo Developer

How to send messages and receive notifications with the Messaging API v2

The GoToConnect Messaging API v2 allows applications to send messages across any type of inbox (shared or private) on any supported channel including SMS, WhatsApp, Facebook Messenger, Instagram, Email, and Web Chat.

In this guide, you will learn how to:

  1. Obtain an access token with the required scopes.
  2. Send messages on each supported channel using ready-to-use curl examples.
  3. Set up a webhook subscription to receive messaging events in real time.

Prerequisites

This article assumes you can make API calls successfully which requires:

Authentication Scopes

Scope Description
messaging.v1.send Send messages through the API
messaging.v1.read Read messages, templates, and attachments
messaging.v1.notifications.manage Manage notifications for messages and attachments

When creating your OAuth client or Personal Access Token, ensure at minimum the messaging.v1.send scope is enabled to follow the sending examples below. The messaging.v1.read scope is required for the Retrieving Attachments section. The messaging.v1.notifications.manage scope is required for the webhook notifications section of this guide.

Note: The Messaging API v2 accepts the same scopes as Messaging API v1. Existing integrations do not need to re-authorize.

Usage Guidelines

The Messaging API assumes clients are implemented in such a way that unknown attributes are ignored. The addition of new response attributes may occur at any time, for any API response, without prior notice. This is not considered a break in backward-compatibility.

Permissions & Touchpoint Configuration

Before sending messages, ensure the caller has the appropriate permissions for the touchpoint used in the from field:

  • Shared inbox (SMS/MMS, Web Chat, Email, WhatsApp, Facebook, Instagram): A phone number must be assigned to an inbox by an administrator. All users with the appropriate role on the inbox can then send and receive messages from that number in a shared environment.
  • Private phone number (SMS/MMS): An administrator must grant texting permissions directly on the phone number for the user. Without this, the API returns a 403 Forbidden error.

Contact your GoTo account administrator to verify these permissions are in place before integrating.

Sending Messages

This section provides a copy-pasteable curl example for sending a message on each supported channel. All examples use the POST /accounts/{accountKey}/messages endpoint with multipart/form-data encoding.

Conversation handling: In most cases, you do not need to specify a conversationId. The API automatically adds sent messages to an existing open conversation between the owner touchpoint and contact touchpoints, or creates a new conversation under the inbox where the owner touchpoint is configured.

Replace {ACCESS_TOKEN} with your Bearer token and {accountKey} with your GoTo account key. Your account key can be retrieved via the Get Me endpoint.

SMS — Plain Text

Send a simple text message to a single phone number.

Touchpoint format: from and to must be E.164 phone numbers starting with +.

curl -X POST 'https://api.goto.com/messaging/v2/accounts/{accountKey}/messages' \
  -H 'Authorization: Bearer {ACCESS_TOKEN}' \
  -F 'request={
    "channel": "SMS",
    "from": "+15145551111",
    "to": ["+15145552222"],
    "type": "simple",
    "components": [{
      "type": "body",
      "components": [{
        "type": "text",
        "attributes": { "text": "Hello from GoTo!" }
      }]
    }]
  };type=application/json'

SMS — MMS with Attachment

Send a picture message by adding an attachments part. The attachmentName in the request must match the filename in the form part.

Touchpoint format: Same as SMS plain text — E.164 phone numbers.

curl -X POST 'https://api.goto.com/messaging/v2/accounts/{accountKey}/messages' \
  -H 'Authorization: Bearer {ACCESS_TOKEN}' \
  -F 'request={
    "channel": "SMS",
    "from": "+15145551111",
    "to": ["+15145552222"],
    "type": "simple",
    "components": [{
      "type": "body",
      "components": [{
        "type": "text",
        "attributes": { "text": "Check out this photo!" }
      }, {
        "type": "image",
        "attributes": { "attachmentName": "photo.jpg" }
      }]
    }]
  };type=application/json' \
  -F 'attachments=@photo.jpg;type=image/jpeg'

SMS supports text and attachments combined in a single message. Max total attachment size: 2 MB.

SMS — Group MMS

Send a message to multiple recipients (up to 10) by adding more entries to the to array.

Touchpoint format: E.164 phone numbers.

curl -X POST 'https://api.goto.com/messaging/v2/accounts/{accountKey}/messages' \
  -H 'Authorization: Bearer {ACCESS_TOKEN}' \
  -F 'request={
    "channel": "SMS",
    "from": "+15145551111",
    "to": ["+15145552222", "+15145553333", "+15145554444"],
    "type": "simple",
    "components": [{
      "type": "body",
      "components": [{
        "type": "text",
        "attributes": { "text": "Hey team, meeting at 3pm!" }
      }]
    }]
  };type=application/json'

SMS is the only channel that supports multiple recipients per message (up to 10).

WhatsApp — Simple Text

Send a plain text message on WhatsApp. If the contact has messaged you within the last 24 hours, a simple message can be sent. Otherwise, a template message is required (see the WhatsApp Outbound Messaging section of the Messaging API v2 reference).

Touchpoint format: from is the numeric Meta WhatsApp phone number ID (not E.164). to is the contact's E.164 phone number or BSUID (Business-Scoped User ID).

curl -X POST 'https://api.goto.com/messaging/v2/accounts/{accountKey}/messages' \
  -H 'Authorization: Bearer {ACCESS_TOKEN}' \
  -F 'request={
    "channel": "WHATSAPP",
    "from": "137169456416846",
    "to": ["+15145552222"],
    "type": "simple",
    "components": [{
      "type": "body",
      "components": [{
        "type": "text",
        "attributes": { "text": "Thanks for reaching out! How can I help?" }
      }]
    }]
  };type=application/json'

WhatsApp does not support text and attachments combined. Send them as separate messages.

WhatsApp — Attachment

Send an attachment message (image, video, audio, or document) on WhatsApp. Include the file as an attachments part and reference it by name in attachmentName.

curl -X POST 'https://api.goto.com/messaging/v2/accounts/{accountKey}/messages' \
  -H 'Authorization: Bearer {ACCESS_TOKEN}' \
  -F 'request={
    "channel": "WHATSAPP",
    "from": "137169456416846",
    "to": ["+15145552222"],
    "type": "simple",
    "components": [{
      "type": "body",
      "components": [{
        "type": "image",
        "attributes": {
          "attachmentName": "photo.jpg",
          "caption": "Check out this image!"
        }
      }]
    }]
  };type=application/json' \
  -F 'attachments=@photo.jpg;type=image/jpeg'

Web Chat

Send a message to an active web chat session.

Touchpoint format: from is the Widget ID. to is the session identifier.

curl -X POST 'https://api.goto.com/messaging/v2/accounts/{accountKey}/messages' \
  -H 'Authorization: Bearer {ACCESS_TOKEN}' \
  -F 'request={
    "channel": "WEB_CHAT",
    "from": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90",
    "to": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
    "type": "simple",
    "components": [{
      "type": "body",
      "components": [{
        "type": "text",
        "attributes": { "text": "Hello! An agent will be with you shortly." }
      }]
    }]
  };type=application/json'

Facebook Messenger

Send a message to a Facebook user who has previously messaged your page.

Touchpoint format: from is your Facebook Page ID. to is the page-scoped user ID.

curl -X POST 'https://api.goto.com/messaging/v2/accounts/{accountKey}/messages' \
  -H 'Authorization: Bearer {ACCESS_TOKEN}' \
  -F 'request={
    "channel": "FACEBOOK",
    "from": "102345678901234",
    "to": ["567890123456789"],
    "type": "simple",
    "components": [{
      "type": "body",
      "components": [{
        "type": "text",
        "attributes": { "text": "Thanks for your message! We will get back to you soon." }
      }]
    }]
  };type=application/json'

Facebook does not support outbound-first messaging. The contact must have messaged your page first.

Instagram

Send a message to an Instagram user who has previously messaged your page.

Touchpoint format: from is your Instagram Page ID. to is the page-scoped user ID.

curl -X POST 'https://api.goto.com/messaging/v2/accounts/{accountKey}/messages' \
  -H 'Authorization: Bearer {ACCESS_TOKEN}' \
  -F 'request={
    "channel": "INSTAGRAM",
    "from": "17841400123456789",
    "to": ["67890123456"],
    "type": "simple",
    "components": [{
      "type": "body",
      "components": [{
        "type": "text",
        "attributes": { "text": "Hi! Thanks for reaching out on Instagram." }
      }]
    }]
  };type=application/json'

Instagram does not support outbound-first messaging. The contact must have messaged your page first.

Email

Reply to an existing email conversation. Email requires a conversationId — you can only send messages within an existing conversation.

Touchpoint format: from and to are email addresses.

curl -X POST 'https://api.goto.com/messaging/v2/accounts/{accountKey}/messages' \
  -H 'Authorization: Bearer {ACCESS_TOKEN}' \
  -F 'request={
    "channel": "EMAIL",
    "from": "support@example.com",
    "to": ["customer@example.com"],
    "conversationId": "987fcdeb-51a2-43d1-b789-123456789abc",
    "type": "simple",
    "components": [{
      "type": "body",
      "components": [{
        "type": "text",
        "attributes": { "text": "Thank you for contacting us. Your issue has been resolved." }
      }]
    }]
  };type=application/json'

Email does not support outbound-first messaging. A conversation must already exist before you can send a message. Use the conversationId from the INBOUND_MESSAGE webhook notification that fires when the customer's email arrives (see Receiving Events below).

Email supports text and attachments combined in a single message, with no size limits on attachments. Add files using the -F 'attachments=@file;type=...' syntax shown in the WhatsApp attachment example above.

Receiving Events with Webhook Notifications

When a subscription is active, the Messaging service delivers real-time events to your webhook as HTTP POST requests. See the Notification Channel overview for details on delivery semantics, retry behavior, and channel lifecycle.

Note: While this guide focuses on webhooks, the Notification Channel API supports additional channel types. Webhooks are recommended for server-to-server integrations.

All webhook notifications share the following envelope structure:

{
  "id": "456e7890-e12b-34d5-a678-901234567890",
  "source": "inbox-notification",
  "type": "INBOUND_MESSAGE",
  "usage": "BACKGROUND",
  "timestamp": "2025-07-31T10:30:00Z",
  "ttl": 604800,
  "content": {
    "accountKey": "9502350087451835131",
    "payload": { }
  }
}

Where:

  • id is a unique identifier for this event.
  • source is always inbox-notification.
  • type identifies the event type. See below for all supported values.
  • usage is always BACKGROUND.
  • timestamp is the ISO 8601 time when the event was created.
  • ttl is the time-to-live for the event in seconds.
  • content.accountKey scopes the event to a specific account.
  • content.payload contains the event-specific data (see the Notification Event Reference below).

Consumers should gracefully handle unknown type values. New event types may be added without prior notice.

Supported Event Types

Event Type Description
INBOUND_MESSAGE A message was received from an external party.
OUTBOUND_MESSAGE A message was sent to an external party.
DELIVERY_STATUS The delivery status of a sent message changed (e.g. delivered, failed).
INBOUND_REACTION A reaction was received on a message from an external party.
OUTBOUND_REACTION A reaction was sent on a message to an external party.

Setting Up Notifications Step by Step

This walkthrough shows how to set up a webhook subscription and receive messaging events end-to-end. Alice is a Super Admin for her organization (accountKey: 9502350087451835131). Her webhook is at https://mycompany.com/sms-webhook.

Step 1 — Create a Notification Channel

Follow the How to create a channel and receiving notifications from GoTo services guide to create a webhook channel and obtain a channelId. You will need it in the next step.

Step 2 — Create an Organization Subscription

Using the channelId obtained in Step 1, create a subscription that covers all touchpoints in Alice's organization. This only needs to be done once.

Note: This endpoint requires a Super Admin bearer token.

curl -X POST "https://api.goto.com/messaging/v2/subscriptions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {ACCESS_TOKEN}" \
  -d '{
    "accountKey": "9502350087451835131",
    "ownerEntity": "9502350087451835131",
    "ownerEntityType": "ORGANIZATION",
    "channelId": "Webhook.cfeb8e9a-495c-4e48-b3bf-fe1b4cf1cf76",
    "eventTypes": ["INBOUND_MESSAGE", "OUTBOUND_MESSAGE", "DELIVERY_STATUS"]
  }'

The HTTP 201 Created response confirms the subscription:

{
  "id": "MTIzNDp4eXo=",
  "ownerEntity": "9502350087451835131",
  "ownerEntityType": "ORGANIZATION",
  "channelId": "Webhook.cfeb8e9a-495c-4e48-b3bf-fe1b4cf1cf76",
  "eventTypes": ["INBOUND_MESSAGE", "OUTBOUND_MESSAGE", "DELIVERY_STATUS"]
}

The subscription is bound to the user who created it. If that user is deactivated or loses Super Admin privileges, notifications will stop. Re-create the subscription with an active Super Admin if this occurs.

Step 3 — Receive an Inbound SMS

When Bob (+15559998888) sends a message to Alice (+15551234567), the webhook receives an HTTP POST with an INBOUND_MESSAGE notification:

{
  "id": "456e7890-e12b-34d5-a678-901234567890",
  "source": "inbox-notification",
  "type": "INBOUND_MESSAGE",
  "usage": "BACKGROUND",
  "timestamp": "2025-07-31T10:30:00Z",
  "ttl": 604800,
  "content": {
    "accountKey": "9502350087451835131",
    "payload": {
      "messageId": "123e4567-e89b-12d3-a456-426614174000",
      "conversationId": "987fcdeb-51a2-43d1-b789-123456789abc",
      "direction": "INBOUND",
      "channel": "SMS",
      "ownerTouchpoint": "+15551234567",
      "contactTouchpoints": ["+15559998888"],
      "authorTouchpoint": "+15559998888",
      "body": "Hello Alice!",
      "attachments": { "count": 0, "items": [] },
      "timestamp": "2025-07-31T10:30:00Z"
    }
  }
}

Your webhook must respond with an HTTP 2XX (e.g. 204 No Content).

Step 4 — Send an SMS Reply

Alice replies to Bob by sending a message via the API, as shown in the Sending Messages section above:

curl -X POST "https://api.goto.com/messaging/v2/accounts/9502350087451835131/messages" \
  -H "Authorization: Bearer {ACCESS_TOKEN}" \
  -F 'request={
    "channel": "SMS",
    "from": "+15551234567",
    "to": ["+15559998888"],
    "type": "simple",
    "components": [{
      "type": "body",
      "components": [{
        "type": "text",
        "attributes": { "text": "Hi Bob!" }
      }]
    }]
  };type=application/json'

The HTTP 201 Created response confirms the sent message:

{
  "id": "234f5678-f90a-12b3-c456-536725285111",
  "conversationId": "987fcdeb-51a2-43d1-b789-123456789abc",
  "accountKey": "9502350087451835131",
  "timestamp": "2025-07-31T10:31:00Z",
  "channel": "SMS",
  "direction": "OUTBOUND",
  "from": "+15551234567",
  "to": ["+15559998888"],
  "body": "Hi Bob!"
}

If the subscription includes OUTBOUND_MESSAGE, the webhook also receives a notification:

{
  "id": "789a1234-b56c-78d9-e012-345678901234",
  "source": "inbox-notification",
  "type": "OUTBOUND_MESSAGE",
  "usage": "BACKGROUND",
  "timestamp": "2025-07-31T10:31:00Z",
  "ttl": 604800,
  "content": {
    "accountKey": "9502350087451835131",
    "payload": {
      "messageId": "234f5678-f90a-12b3-c456-536725285111",
      "conversationId": "987fcdeb-51a2-43d1-b789-123456789abc",
      "direction": "OUTBOUND",
      "channel": "SMS",
      "ownerTouchpoint": "+15551234567",
      "contactTouchpoints": ["+15559998888"],
      "authorTouchpoint": "+15551234567",
      "body": "Hi Bob!",
      "attachments": { "count": 0, "items": [] },
      "timestamp": "2025-07-31T10:31:00Z"
    }
  }
}

Step 5 — Receive a Delivery Status Update

Shortly after the outbound message is sent, the webhook receives a DELIVERY_STATUS notification indicating whether the message was successfully delivered or failed:

{
  "id": "abc12345-d678-90ef-a123-456789012345",
  "source": "inbox-notification",
  "type": "DELIVERY_STATUS",
  "usage": "BACKGROUND",
  "timestamp": "2025-07-31T10:32:00Z",
  "ttl": 604800,
  "content": {
    "accountKey": "9502350087451835131",
    "payload": {
      "messageId": "234f5678-f90a-12b3-c456-536725285111",
      "conversationId": "987fcdeb-51a2-43d1-b789-123456789abc",
      "ownerTouchpoint": "+15551234567",
      "contactTouchpoint": "+15559998888",
      "timestamp": "2025-07-31T10:32:00Z",
      "deliveryStatus": {
        "code": 1000,
        "description": "DELIVERED",
        "retryable": false
      }
    }
  }
}

See the deliveryStatus.code table in the Subscriptions API reference for all status codes.

Notification Event Reference

This section documents the payload of each event type delivered to your webhook.

INBOUND_MESSAGE

Delivered when a message is received from an external party. The payload fields use ownerTouchpoint (the inbox/touchpoint that owns the conversation), contactTouchpoints (the external party), and authorTouchpoint (who sent this specific message).

{
  "id": "456e7890-e12b-34d5-a678-901234567890",
  "source": "inbox-notification",
  "type": "INBOUND_MESSAGE",
  "usage": "BACKGROUND",
  "timestamp": "2025-07-31T10:30:00Z",
  "ttl": 604800,
  "content": {
    "accountKey": "9502350087451835131",
    "payload": {
      "messageId": "123e4567-e89b-12d3-a456-426614174000",
      "conversationId": "987fcdeb-51a2-43d1-b789-123456789abc",
      "direction": "INBOUND",
      "channel": "SMS",
      "ownerTouchpoint": "+15551234567",
      "contactTouchpoints": ["+15559998888"],
      "authorTouchpoint": "+15559998888",
      "body": "Hello!",
      "attachments": { "count": 0, "items": [] },
      "timestamp": "2025-07-31T10:30:00Z"
    }
  }
}

OUTBOUND_MESSAGE

Delivered when a message is sent to an external party. authorTouchpoint is the owner's touchpoint and direction is OUTBOUND.

{
  "id": "789a1234-b56c-78d9-e012-345678901234",
  "source": "inbox-notification",
  "type": "OUTBOUND_MESSAGE",
  "usage": "BACKGROUND",
  "timestamp": "2025-07-31T10:31:00Z",
  "ttl": 604800,
  "content": {
    "accountKey": "9502350087451835131",
    "payload": {
      "messageId": "234f5678-f90a-12b3-c456-536725285111",
      "conversationId": "987fcdeb-51a2-43d1-b789-123456789abc",
      "direction": "OUTBOUND",
      "channel": "SMS",
      "ownerTouchpoint": "+15551234567",
      "contactTouchpoints": ["+15559998888"],
      "authorTouchpoint": "+15551234567",
      "body": "Hi Bob!",
      "attachments": { "count": 0, "items": [] },
      "timestamp": "2025-07-31T10:31:00Z"
    }
  }
}

DELIVERY_STATUS

Delivered when the delivery status of an outbound message changes (e.g. a failure is detected). See the deliveryStatus.code table in the Subscriptions API reference for all status codes.

{
  "id": "abc12345-d678-90ef-a123-456789012345",
  "source": "inbox-notification",
  "type": "DELIVERY_STATUS",
  "usage": "BACKGROUND",
  "timestamp": "2025-07-31T10:32:00Z",
  "ttl": 604800,
  "content": {
    "accountKey": "9502350087451835131",
    "payload": {
      "messageId": "234f5678-f90a-12b3-c456-536725285111",
      "conversationId": "987fcdeb-51a2-43d1-b789-123456789abc",
      "ownerTouchpoint": "+15551234567",
      "contactTouchpoint": "+15559998888",
      "timestamp": "2025-07-31T10:32:00Z",
      "deliveryStatus": {
        "code": 4000,
        "description": "INVALID_DESTINATION",
        "retryable": false
      }
    }
  }
}

INBOUND_REACTION

Delivered when an external party reacts to a message (e.g. a WhatsApp emoji reaction).

{
  "id": "bcd23456-e789-01fg-b234-567890123456",
  "source": "inbox-notification",
  "type": "INBOUND_REACTION",
  "usage": "BACKGROUND",
  "timestamp": "2025-07-31T10:33:00Z",
  "ttl": 604800,
  "content": {
    "accountKey": "9502350087451835131",
    "payload": {
      "messageId": "123e4567-e89b-12d3-a456-426614174000",
      "conversationId": "987fcdeb-51a2-43d1-b789-123456789abc",
      "emoji": "👍",
      "timestamp": "2025-07-31T10:33:00Z"
    }
  }
}

OUTBOUND_REACTION

Delivered when an agent sends a reaction to an external party's message.

{
  "id": "cde34567-f890-12gh-c345-678901234567",
  "source": "inbox-notification",
  "type": "OUTBOUND_REACTION",
  "usage": "BACKGROUND",
  "timestamp": "2025-07-31T10:34:00Z",
  "ttl": 604800,
  "content": {
    "accountKey": "9502350087451835131",
    "payload": {
      "messageId": "234f5678-f90a-12b3-c456-536725285111",
      "conversationId": "987fcdeb-51a2-43d1-b789-123456789abc",
      "emoji": "✅",
      "timestamp": "2025-07-31T10:34:00Z"
    }
  }
}

Retrieving Attachments

When an INBOUND_MESSAGE or OUTBOUND_MESSAGE notification includes attachments, the attachments field contains metadata for each file:

"attachments": {
  "count": 1,
  "items": [
    {
      "name": "document.pdf",
      "contentType": "application/pdf",
      "size": 1024000,
      "attachmentId": "123e4567-e89b-12d3-a456-426614174000"
    }
  ]
}

To download the file, use the GET /accounts/{accountKey}/attachments/{attachmentId} endpoint with the attachmentId from the notification. The endpoint returns an HTTP 302 redirect to a pre-signed download URL.

Required scope: messaging.v1.read

curl -L -X GET 'https://api.goto.com/messaging/v2/accounts/{accountKey}/attachments/123e4567-e89b-12d3-a456-426614174000' \
  -H 'Authorization: Bearer {ACCESS_TOKEN}' \
  -o document.pdf

The pre-signed URL expires after 60 seconds. Follow the redirect immediately and do not store the URL for later use. The -L flag tells curl to follow the redirect automatically.