openapi: 3.2.0 info: title: Inbox 2.0 API Reference Vai Webhooks API description: Inbox 2.0 API Reference version: v1 x-logo: url: static/hootsuite-logo.png contact: email: dev.support@hootsuite.com license: name: Hootsuite Developer Terms and API License Agreement url: https://hootsuite.com/legal/dev-api-terms servers: - url: https://platform.hootsuite.com description: Inbox 2.0 production server security: - bearer-token: [] tags: - name: vai_webhooks x-displayName: Webhooks description: "### Webhook authentication\n\nWhen setting up a virtual agent, you received a secret key that you can use to verify whether an incoming webhook\nrequest really comes from Inbox 2.0 without alterations. In the request headers of each webhook call is the\n`X-Hootsuite-Signature`. This contains an `HMAC-SHA256` signature based on the body of the request. \nBoth the secret key you received and the signature are encoded as hexadecimal strings. \nMost languages come with libraries out of the box to verify this signature. \nHere is some sample code to verify it in Node.js:\n\n```javascript\nconst secret = \"...\"; // do not share!\nconst expectedSignature = request.headers[\"X-Hootsuite-Signature\"];\nconst actualSignature = crypto\n.createHmac(\"sha256\", Buffer.from(secret, \"hex\"))\n.update(request.body, \"utf-8\")\n.digest(\"hex\");\nif (actualSignature !== expectedSignature) {\nthrow new createError.Unauthorized(\"X-Hootsuite-Signature wrong\");\n}\n```\n\nNote: Make sure you calculate the signature off the body as is, before you deserialize it from JSON. \nDuring the calculation of the signature, all white space is considered significant.\n\nAs part of the request body, you will find a timestamp. This is the time a request was sent. To prevent replay attacks,\nwe recommend verifying that this timestamp is no older than 5 minutes:\n\n```javascript\nif (moment(JSON.parse(request.body).timestamp).isBefore(moment().subtract(5, \"minutes\"))) {\n throw new createError.Unauthorized(\"Request too old\");\n}\n```\n\n### Events\n\nWhen a conversation is assigned to the virtual agent you registered in the previous section, Inbox 2.0 sends you an event via the URL you configured.\n\nThree important events are sent:\n\n - `CONVERSATION_STARTED`\n - `CONVERSATION_DELEGATED`\n - `INBOUND_MESSAGE_RECEIVED`\n\n### Common fields\n\nAll events have certain common fields:\n\n - type: A string that defines what kind of event occurred (currently `CONVERSATION_STARTED`, `CONVERSATION_DELEGATED`, or `INBOUND_MESSAGE_RECEIVED`). \n New events can be added in the future. Avoid responding with an error to unknown\n values; instead, ignore them. Depending on this type, the structure of data will be different.\n - version: A number designating the version of the type of request. Currently, the version is always 1. Versions will be\n used in the future for introducing non-backward-compatible changes.\n - idempotencyKey: A string that uniquely identifies each event. When a timeout occurs when sending you the event, (or we\n receive an error response), we will retry the event. This key can help you to ensure that a request is processed only\n once.\n - timestamp: The timestamp when we sent the request. This is used to counter possible replay attacks.\n - data: An object that contains structured data for the specific type. For example, an `INBOUND_MESSAGE_RECEIVED` type\n event has fields such as `conversationId` and `message`. Fields may be added in the future.\n\n### Requirements\n\nTo provide a good customer experience, some non-functional requirements are imposed on the webhook. When a webhook is\nsent, you have 10 seconds to respond with a 200 OK. If a timeout occurs, we will retry 3 times using an exponential\nbackoff (up to 2 seconds). If the failures persisted during the retries, the assigned conversation will be handed over\nto a human agent by placing it in the New queue.\n\nWhen the contact sends a message through Inbox 2.0, by default we expect the virtual agent to reply to that message\nwithin 5 minutes (using either the response to the webhook call or the REST API). You can configure the timeout on the\nsettings page for your virtual agent (Timeout virtual agent). If the virtual agent does not answer the contact, by\ndefault the conversation is placed in the New queue for a human agent to pick up. This can also be configured\nin `Settings`. If you prefer, you can automatically resolve the conversation and send a message to the contact (such\nas \"Please try again in a little while\"). The virtual agent could also decide to immediately return control by\nsending `RESOLVED` or `HANDOVER` in the `complete` field.\n\nSimilarly, after a `CONVERSATION_DELEGATED` event, your virtual agent has 5 minutes to pose a question to the contact by\ndefault. If the virtual agent fails to do this, the conversation is handed back to the previous owner of the\nconversation or placed in the New queue, depending on the handoff rule.\n\n### Response\n\nYour response to the webhook should be a status `200 OK`. In the body, you can return the response you want to send to the contact:\n \n ```json\n {\n \"sendMessage\": {\n \"text\": \"Hi! How can I help you?\",\n \"attachment\": \"funny_cat.gif\"\n },\n \"applyTopics\": [\n \"Hotel Reservation\"\n ],\n \"applyTags\": [\n \"Happy\"\n ],\n \"setContactAttributes\": {\n \"account_number\": \"19758293529351\"\n },\n \"complete\": \"HANDOVER\"\n }\n ```\n\n- sendMessage: (Optional) The message you want to send to the contact. You can send only text, only an attachment, or\n both at the same time. If you want to send an attachment, you must upload it first, so we recommend using the\n asynchronous flow.\n- applyTopics: (Optional) The list of topics you want to apply to the conversation (the intent or action that your\n Virtual Agent matched). Topics that do not exist in Inbox 2.0 will be ignored.\n- applyTags: The list of tags you want to apply to the message from the contact. Tags that do not exist in Inbox 2.0\n will be ignored. It's only possible to use `applyTags` in response to an `INBOUND_MESSAGE_RECEIVED`. It's also\n possible to tag a specific message by specifying the `messageId`. In that case you can respond\n using \n \n ```json\n {\n \"applyTags\": [{\n \"messageId\": \"cc75552a-1a78-11e9-855e-6d1e71016abf\", \n \"tag\": \"Happy\"\n }]\n }\n ```\n- setContactAttributes: The attributes you want to set on a contact. The object is a map between the attribute\n definition's alias and value to set.\n- complete: (Optional) This can be either `HANDOVER` if you want to give the conversation to another agent,\n or `RESOLVED` if you want to resolve the conversation. When you pass HANDOVER, the handoff rule you configured in\n settings determine what will happen next. If the handoff rule is \"No one,\" the conversation is placed in the New\n queue without an owner. Any human agent can pick up the conversation. If the rule has been set to \"Previous agent,\"the\n conversation will be assigned back to the previous human agent. If there was no previous agent, the conversation is\n placed in the New queue without an owner.\n \n If you are integrating with an asynchronous bot platform, you can simply return an empty JSON body {} and send this\n message using a POST request. We also recommend using the REST API when you want to send an attachment. You can respond\n with {}, upload an attachment using a PUT request, and then send the attachment using a POST request.\n" paths: {} webhooks: vai-conversation-started: post: summary: 'CONVERSATION_STARTED ' description: '### CONVERSATION_STARTED The `CONVERSATION_STARTED` event is sent when a conversation is assigned to your `Inception virtual agent`. You will only receive messages and will only be able to reply to messages as long as the conversation is assigned to the `Inception virtual agent`. As part of this event, you receive extra information about the contact profile trying to contact you, the channel over which they are contacting you, and the contact attributes. The contact attributes include medium specific information, information added manually before, and information coming from previous CRM lookups. Note that the `Inception Virtual Agent` will receive the `CONVERSATION_STARTED` event before any configured CRM lookup is performed and therefore the event will not include any new information from the CRM. After the `CONVERSATION_STARTED` event, you''ll receive an `INBOUND_MESSAGE_RECEIVED` event for each message the contact sends, starting from the beginning of the conversation. ' operationId: vaiConversationStarted security: - SharedSecret: [] tags: - vai_webhooks requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ConversationStartedEvent' responses: '200': description: 'Your response to the webhook should be a status `200 OK`. In the body, you can optionally return the response you want to send to the contact. ' content: application/json;charset=utf-8: schema: $ref: '#/components/schemas/ConversationManipulation' 4xx: description: 'The webhook event is not processed successfully. See [Add a Virtual Agent](#section/Add-a-Virtual-Agent) on how to configure error behavior. ' 5xx: description: 'The webhook event is not processed successfully. See [Add a Virtual Agent](#section/Add-a-Virtual-Agent) on how to configure error behavior. ' vai-conversation-delegated: post: summary: 'CONVERSATION_DELEGATED ' description: '### CONVERSATION_DELEGATED The `CONVERSATION_DELEGATED` event is sent when a conversation is assigned to your Delegation virtual agent. Like the `CONVERSATION_STARTED` event, this contains extra information about the contact profile trying to contact you, the channel over which they are contacting you, and the contact attributes. After the `CONVERSATION_DELEGATED` event, you''ll receive an `INBOUND_MESSAGE_RECEIVED` event for each message the contact sends, starting from when the conversation is assigned to the virtual agent. Unlike the `CONVERSATION_STARTED` event, the `CONVERSATION_DELEGATED` event is often not immediately followed by an `INBOUND_MESSAGE_RECEIVED`. Instead, it is expected that the virtual agent will ask the first question to the contact. After you''ve received a `CONVERSATION_DELEGATED` event, you have 2 minutes to ask this first question. See the requirements for more details. ' operationId: vaiConversationDelegated security: - SharedSecret: [] tags: - vai_webhooks requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ConversationDelegatedEvent' responses: '200': description: 'Your response to the webhook should be a status `200 OK`. In the body, you have the option to return the response you want to send to the contact. ' content: application/json;charset=utf-8: schema: $ref: '#/components/schemas/ConversationManipulation' 4xx: description: 'The webhook event is not processed successfully. See [Add a virtual agent](#section/Add-a-Virtual-Agent) on how to configure error behavior. ' 5xx: description: 'The webhook event is not processed successfully. See [Add a virtual agent](#section/Add-a-Virtual-Agent) on how to configure error behavior. ' vai-inbound-message-received: post: summary: 'INBOUND_MESSAGE_RECEIVED ' description: '### INBOUND_MESSAGE_RECEIVED After the conversation is assigned to the virtual agent, and after you received a `CONVERSATION_STARTED` or a `CONVERSATION_DELEGATED` event, you will start receiving `INBOUND_MESSAGE_RECEIVED` events. These events occur every time the contact sends a message. Note that we send the conversation topics with each `INBOUND_MESSAGE_RECEIVED` event. ' operationId: vaiInboundMessageReceived security: - SharedSecret: [] tags: - vai_webhooks requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/InboundMessageReceivedEvent' responses: '200': description: 'Your response to the webhook should be a status `200 OK`. In the body, you can optionally return the response you want to send to the contact. ' content: application/json;charset=utf-8: schema: $ref: '#/components/schemas/ConversationManipulation' 4xx: description: 'The webhook event is not processed successfully. See [Add a Virtual Agent](#section/Add-a-Virtual-Agent) on how to configure error behavior. ' 5xx: description: 'The webhook event is not processed successfully. See [Add a Virtual Agent](#section/Add-a-Virtual-Agent) on how to configure error behavior. ' components: schemas: Message: type: object properties: text: type: string example: Hi! How can I help you? attachment: type: string example: funny_cat.gif description: 'The messages you want to send ' ConversationManipulation: type: object properties: indicateTyping: type: string enum: - START - STOP description: 'Indicate whether you are typing ' sendMessages: type: array items: $ref: '#/components/schemas/Message' description: 'The messages you want to send ' sendMessage: $ref: '#/components/schemas/Message' description: 'The message you want to send ' applyTopics: type: array items: type: string example: - Hotel Reservation description: 'The topics you want to apply to the conversation ' applyTags: type: array items: type: string example: - Happy description: 'The tag you want to apply to the latest message ' applyConversationNotes: type: array items: type: object properties: text: type: string example: Some note to add description: 'The notes you want to apply to the conversation ' setContactAttributes: type: object additionalProperties: type: string example: account_number: '19758293529351' description: 'The attributes you want to apply to the contact. The key should match "^[a-zA-Z0-9_]+$" ' complete: type: string enum: - HANDOVER - RESOLVED description: 'Finish a conversation by handing it off to a human agent or by marking the conversation resolved ' InboundMessageReceivedEvent: allOf: - $ref: '#/components/schemas/VaiWebhookEvent' - properties: type: type: string enum: - INBOUND_MESSAGE_RECEIVED data: type: object properties: conversationId: type: string description: 'An ID indicating the conversation this message is part of. ' example: 0-01d90bc1b13-000-9a9ca0d8 message: type: object description: 'The actual message content. ' properties: messageId: type: string example: cc75552a-1a78-11e9-855e-6d1e71016abf text: type: string example: Hello payload: type: string example: description: 'Custom data provided by the virtual agent when sending a rich message with either quick reply or postback buttons ' trigger: type: object x-nullable: true description: 'An optional trigger providing context on the object that triggered this inbound message. ' properties: type: type: string x-nullable: true description: Type of the object that triggered this inbound message. example: POST enum: - REEL - STORY - POST action: type: string x-nullable: true description: Action on the object that triggered this inbound message. example: REPLY enum: - SHARE - MENTION - REPLY conversationTopics: type: array items: type: string example: - Topic 1 - Topic 2 channel: type: object properties: id: type: string example: 0-02534c5ac04-000-261ad914 name: type: string example: Inbox 2.0 support channel contactProfile: type: object description: 'Information on the contact triggering this inbound message. ' properties: id: type: string example: 693ed54b-a426-11eb-9363-cffd945b4b2f mediumContactProfileId: type: string example: bb7b0f8f00cc989b97f0725b primaryIdentifier: type: string example: John Smith secondaryIdentifier: type: string example: '+32439487192' pictureUrl: type: string example: https://example.com/my-picture.png medium: type: object properties: id: type: string example: facebook ConversationDelegatedEvent: allOf: - $ref: '#/components/schemas/VaiWebhookEvent' - properties: type: type: string enum: - CONVERSATION_DELEGATED data: $ref: '#/components/schemas/ConversationData' ConversationData: type: object properties: conversationId: type: string example: 0ad42eef-a806-11eb-9642-f1ceb2f21def medium: type: object properties: id: type: string example: fb channel: type: object properties: id: type: string example: 0-02534c5ac04-000-261ad914 name: type: string example: Inbox 2.0 support channel language: type: string example: en contactProfile: type: object properties: id: type: string example: 693ed54b-a426-11eb-9363-cffd945b4b2f mediumContactProfileId: type: string example: bb7b0f8f00cc989b97f0725b primaryIdentifier: type: string example: John Smith secondaryIdentifier: type: string example: '+32439487192' pictureUrl: type: string example: https://example.com/my-picture.png contactAttributes: type: array items: type: object properties: attribute: type: string example: null value: type: string source: type: string enum: - AGENT - MEDIUM - CRM_CONFIRMED example: - attribute: company value: My company source: AGENT - attribute: fb-profile-name value: John Smith source: MEDIUM - attribute: fb-profile-image value: https://image.com/sticky/default_profile_images/default_profile_normal.png source: MEDIUM - attribute: email value: john.smith@hootsuite.com source: CRM_CONFIRMED - attribute: first_name value: John source: CRM_CONFIRMED - attribute: last_name value: Smith source: CRM_CONFIRMED - attribute: company value: Hootsuite source: CRM_CONFIRMED VaiWebhookEvent: type: object properties: timestamp: type: string description: 'The timestamp when we sent the request. This is used to counter possible replay attacks. ' format: date-time example: '2022-04-28T09:43:26.635984618Z' idempotencyKey: type: string description: 'A string that uniquely identifies each event. When a timeout occurs when sending you the event, (or we receive an error response), we will retry the event. This key can help you to ensure that a request is processed only once. ' example: 6ebc6a78-d9e9-48be-b172-51eda40b7af8 version: type: integer description: 'A number designating the version of the type of request. Currently, the version is always 1. Versions will be used in the future for introducing non-backward compatible changes. ' example: 1 ConversationStartedEvent: allOf: - $ref: '#/components/schemas/VaiWebhookEvent' - properties: type: type: string enum: - CONVERSATION_STARTED data: $ref: '#/components/schemas/ConversationData' securitySchemes: bearer-token: type: http scheme: bearer basic-auth: type: http scheme: basic Oauth2ClientCredentials: type: oauth2 flows: clientCredentials: tokenUrl: TO_BE_CONFIGURED_IN_INBOX_2_0 scopes: some_scope: TO_BE_CONFIGURED_IN_INBOX_2_0 SharedSecret: type: apiKey in: header name: X-Hootsuite-Signature x-provenance: generated: '2026-08-13' method: searched source: https://apidocs.hootsuite.com/docs/api/inbox/openapi/openapi.yaml note: Verbatim first-party OpenAPI 3.1 for the Hootsuite Inbox 2.0 API (formerly Sparkcentral), linked as service-desc for anchor https://platform.hootsuite.com/inbox/v1/ in Hootsuite's RFC 9727 API catalog at https://www.hootsuite.com/.well-known/api-catalog. ownership: servers[] https://platform.hootsuite.com, contact dev.support@hootsuite.com, license "Hootsuite Developer Terms and API License Agreement" - Hootsuite's own contract. x-tagGroups: - name: General tags: - rest-api-authentication - name: CRM API tags: - crm_introduction - crm_webhooks - crm_rest_api - name: Virtual Agent API tags: - vai_introduction - vai_webhooks - vai_rest_api - name: Real-time metrics API tags: - real_time_metrics_introduction - real_time_metrics_rest_api - name: User Presence API tags: - user_presence_introduction - user_presence_rest_api - name: Queue API tags: - queue_introduction - queue_rest_api - name: Proactive messaging API tags: - proactive_messaging_introduction - proactive_messaging_rest_api - name: Messenger SDK tags: - messenger_introduction - messenger_web_sdk