--- title: "Webhooks for Conversational WhatsApp" slug: "webhooks-for-conversational-whatsapp" description: "Documents Conversational WhatsApp webhooks: sent/read/delivered/failed and reply events, template message data, and managing webhooks via the OAuth 2.0 API." tags: ["whatsapp webhooks", "whatsapp conversational"] updated: 2026-11-02T12:55:14Z published: 2026-11-02T12:55:14Z canonical: "academy.insiderone.com/webhooks-for-conversational-whatsapp" --- > ## Documentation Index > Fetch the complete documentation index at: https://academy.insiderone.com/llms.txt > Use this file to discover all available pages before exploring further. # Webhooks for Conversational WhatsApp Insider One processes the messages you have sent by queuing them and prioritizing them according to the number of messages that can be sent per second. Therefore, when you send the message to Insider One, you will get a return with a tracking key. This tracking key will be included in all webhook events of the message you sent. To define your webhook address and token, refer to the [Save Webhook URL](/v1/docs/webhooks-for-conversational-whatsapp#save-webhook-url) step. Your queued messages are processed and forwarded to WhatsApp. If your message is successfully accepted by WhatsApp, a message like the one below will be sent to your webhook address: | Parameter | Description | Data Type | Required | | --- | --- | --- | --- | | messaging_product | whatsapp | String | Yes | | contacts | Contact information | Array | Yes | | input | User's phone number | String | Yes | | wa_id | The customer's WhatsApp ID. A business can respond to a message using this ID. | String | Yes | | messages | | Array | Yes | | id | The ID for the message that was received by the business. You could use the messages endpoint to mark this specific message as read. | String | Yes | | Key | Message tracking key, created by Insider One | String | Yes | ```json {  "messaging_product": "whatsapp",  "contacts": [    {      "input": "{{USER_PHONE_NUMBER}}",      "wa_id": "{{WHATSAPP_ID}}"    }  ],  "messages": [    {      "id": "{{MESSAGE_ID}}"    }  ],  "key": "whatsapp-***********" } ``` If any error is received from WhatsApp related to your message, a message like the one below will be sent to your webhook by Insider One. | Parameter | Description | Data Type | Required | | --- | --- | --- | --- | | error | The object that contains information about the error | Object | Yes | | message | Error code message, such as Rate limit hit. Note that the message property in API error response payloads pre-pends this value with a # symbol and the error code in parentheses, such as (#130429) Rate limit hit. | String | Yes | | type | Error type | String | Yes | | code | Error code | Integer | Yes | | fbtrace_id | Trace ID you can include when contacting [Direct Support](https://business.facebook.com/direct-support). The ID might be useful to debug the error. | String | Yes | | Key | Message tracking key, created by Insider One | String | Yes | ```json {  "error": {    "message": "{{ERROR_MESSAGE}}",    "type": "{{ERROR_TYPE}}",    "code": {{ERROR_CODE}},    "fbtrace_id": "{{FBTRACE_ID}}"  },  "key": "whatsapp-***********" } ``` ## Sample Error Codes When you send WhatsApp messages via Insider One, any errors returned from WhatsApp (**Cloud API** or **On-Premise**) are forwarded to your webhook. Below are sample error responses you may encounter, grouped by category. > [!WARNING] > Important Note on `error_data` > > The `error_data` field is optional. Meta may include it for some errors to provide additional context (for example, message throughput details) and omit it for others. Always rely on the code and message fields as the primary source of information, and treat `error_data`as supplementary metadata when available. > > Below you can see a sample error response with the `error_data` field included: > > ```json > { >  "error": { >    "message": "(#130429) Rate limit hit", >    "type": "OAuthException", >    "code": 130429, >    "error_data": { >      "messaging_product": "whatsapp", >      "details": "Cloud API message throughput reached." >    } >  } > } > ``` ### Cloud API Error Codes The following are sample error responses you might receive when using the WhatsApp Cloud API via Insider One. #### Failure response ```json {  "message": "failed to validate the webhook",  "errorCode": "2020" } ``` #### Success response (HTTP 200) ```json {  "message": "Settings updated successfully" } ``` #### Authorization Errors ```json {  "error": {    "message": "We were unable to authenticate the app user",    "type": "OAuthException",    "code": 0  } } ``` ```json {  "error": {    "message": "Your access token has expired",    "type": "OAuthException",    "code": 190  } } ``` #### Rate Limiting Errors ```json {  "error": {    "message": "(#130429) Rate limit hit",    "type": "OAuthException",    "code": 130429,    "error_data": {      "messaging_product": "whatsapp",      "details": "Cloud API message throughput reached."    }  } } ``` ```json {  "error": {    "message": "Spam rate limit hit",    "code": 131048,    "error_data": {      "messaging_product": "whatsapp",      "details": "Too many messages sent from this phone number in a short period of time."    }  } } ``` #### Account and Permission Errors ```json {  "error": {    "message": "Access denied",    "code": 131005  } } ``` ```json {  "error": {    "message": "Account has been blocked for policy violations",    "code": 368  } } ``` ### On-Premise API Error Codes The following are sample error responses you might receive when using the WhatsApp On-Premise API via Insider One. #### Client Errors (400-499) ```json {  "errors": [    {      "code": 402,      "title": "Business eligibility issue",      "details": "Payment issue preventing message delivery"    }  ] } ``` ```json {  "errors": [    {      "code": 408,      "title": "Message not valid",      "details": "Message has been pending validation too long"    }  ] } ``` ```json {  "errors": [    {      "code": 429,      "title": "Rate limit exceeded",      "details": "Too many requests in a given amount of time"    }  ] } ``` #### Template Errors ```json {  "errors": [    {      "code": 2000,      "title": "Template parameter mismatch",      "details": "Number of parameters provided does not match expected parameter count"    }  ] } ``` ```json {  "errors": [    {      "code": 2060,      "title": "Template rejected",      "details": "Template has been rejected and cannot be used"    }  ] } ``` #### Server Error (500) ```json {  "errors": [    {      "code": 500,      "title": "Internal server error",      "details": "Temporary server issue, please retry"    }  ] } ``` Refer to [Failed Event](/v1/docs/webhooks-for-conversational-whatsapp#failed-event) for the webhook payload structure where these errors are returned. > [!NOTE] > Refer to [Meta’s Error Codes guide](https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes) for further information. ## Save Webhook URL You can define your webhook address and token. ### Headers | Header | Sample Value | Description | | --- | --- | --- | | X-INS-AUTH-KEY | 1a2b3c4d5e6f | This key is required to authorize your request. Refer to [API Authentication Tokens](https://academy.insiderone.com/docs/api-authentication-tokens) to generate your token. | | Content-Type | application/json | This header specifies the media type of the resource. | ### The Body Parameters | Parameter | Description | Data Type | Required | | --- | --- | --- | --- | | isActive | Option to turn on transactional message sending. | Boolean | Yes | | url | Your webhook URL | String | Yes | | apiKey | Your webhook Bearer token | String | Yes | ### Sample Request ```bash curl --location 'https://whatsapp.useinsider.com/v1/conversational/settings/update' \        --header 'Content-Type: application/json' \        --header 'x-ins-auth-key: INS.**************************************' \        --data '{        "isActive": true,        "webhookUrl": "{{YOUR_WEBHOOK_URL}}",        "apiKey": "{{YOUR_WEBHOOK_BEARER_TOKEN_KEY}}"        }' ``` Your system must respond with a **200 OK** status to our request for the webhook to be saved and function correctly. If you receive any response other than 200 from the settings endpoint, it indicates an issue with the URL you provided. Please review the error message returned and verify your endpoint accordingly. ## Sent/Read/Delivered Events | Parameter | Description | Data Type | Required | | --- | --- | --- | --- | | entry[0].changes[0].value.metadata.display_phone_number | The phone number that is displayed for a business | String | Yes | | entry[0].changes[0].value.metadata.display_phone_number_id | ID for the phone number. A business can respond to a message using this ID. | String | Yes | | entry[0].changes[0].value.statuses[0].id | The ID for the message that was received by the business. You could use the messages endpoint to mark this specific message as read. | String | Yes | | entry[0].changes[0].value.statuses[0].recipient_id | The WhatsApp ID for the customer of the business, which is subscribed to the webhooks, sent to the customer | String | Yes | | entry[0].changes[0].value.statuses[0].status | read/sent/delivered | String | Yes | | key | Message tracking key, created by Insider One | String | Yes | ```json {  "entry": [    {      "changes": [        {          "field": "messages",          "value": {            "messaging_product": "whatsapp",            "metadata": {              "display_phone_number": "{{SENDER_PHONE_NUMBER}}",              "phone_number_id": "{{SENDER_PHONE_NUMBER_ID}}"            },            "statuses": [              {                "id": "{{MESSAGE_ID}}",                "recipient_id": "{{PHONE_NUMBER}}",                "status": "{{STATUS}}",                "timestamp": "1111111111"              }            ]          }        }      ],      "id": "111111111"    }  ],  "key": "whatsapp-***********",  "object": "whatsapp_business_account" } ``` ## Template Message Webhook Events Insider One now helps you identify which template message triggered a chatbot. Each time a template message is sent, Insider One forwards the template information to your defined webhook. This lets you match chatbot responses with the right template inside your system. Regardless of how the message content is triggered (whether through single, transactional, etc., via InOne), all messages classified as template message types will be transmitted to the previously defined webhook URL. The content sent will generally mirror the message sending content dispatched to Meta, with the following additions based on your request: - **timestamp**: Indicates the exact time the message was sent to Meta. - **content**: Contains the template's message content. > [!NOTE] > Refer to [Send Conversational WhatsApp Template Messages to Chatbot Systems](/v1/docs/send-conversational-whatsapp-template-messages-to-chatbot-systems) for further information. ## Failed Event | Parameter | Description | Data Type | Required | | --- | --- | --- | --- | | entry[0].changes[0].value.metadata.display_phone_number | The phone number that is displayed for a business | String | Yes | | entry[0].changes[0].value.metadata.display_phone_number_id | ID for the phone number. A business can respond to a message using this ID. | String | Yes | | entry[0].changes[0].value.statuses[0].id | The ID for the message that was received by the business. You could use the messages endpoint to mark this specific message as read. | String | Yes | | entry[0].changes[0].value.statuses[0].recipient_id | The WhatsApp ID for the customer that the business, that is subscribed to the webhooks, sent to the customer | String | Yes | | entry[0].changes[0].value.statuses[0].status | failed | String | Yes | | entry[0].changes[0].value.statuses[0].errors | | Array | Yes | | code | Error code. Integer Example: 130429 | Integer | Yes | | title | Error code title Example: Rate limit hit | String | Yes | | href | Document link with description of the error | String | Yes | | key | Message tracking key, created by Insider One | String | Yes | > [!WARNING] > *Delivery Report Missing* refers to messages for which no delivery report is received from Meta. Since Meta does not send any events for these error messages, they are not delivered to you through either the Transactional API or the Conversational API. ```json {  "key": "whatsapp-**************",  "object": "whatsapp_business_account",  "entry": [    {      "id": "1231313123",      "changes": [        {          "value": {            "messaging_product": "whatsapp",            "metadata": {              "display_phone_number": "{{SENDER_PHONE_NUMBER}}",              "phone_number_id": "{{SENDER_PHONE_NUMBER_ID}}"            },            "statuses": [              {                "id": "{{MESSAGE_ID}}",                "status": "failed",                "timestamp": "1671526301",                "recipient_id": "RECIPIENT_ID",                "errors": [                  {                    "code": {{ERROR_CODE}},                    "title": "{{ERROR_MESSAGE}}",                    "href": "https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes/"                  }                ]              }            ]          },          "field": "messages"        }      ]    }  ] } ``` > [!WARNING] > `href`is not returned for every error. When available, it provides a reference link to Meta's documentation for the specific error code. You can use this link to find more information about the error, but do not rely on href when handling failures. Use `code` and `title` as the primary error fields. ## User Reply with Text Event | Parameter | Description | Data Type | Required | | --- | --- | --- | --- | | entry[0].changes[0].value.metadata.display_phone_number | The phone number that is displayed for a business | String | Yes | | entry[0].changes[0].value.metadata.display_phone_number_id | ID for the phone number. A business can respond to a message using this ID. | String | Yes | | entry[0].changes[0].value.contacts[0].wa_id | The user’s phone number. This value can be empty for a user who adopted a username and did not share their phone number. | String | Yes | | entry[0].changes[0].value.contacts[0].user_id | The user’s BSUID (Business-Scoped User ID). | String | Yes | | entry[0].changes[0].value.contacts[0].profile.name | The user’s WhatsApp display name. | String | Yes | | entry[0].changes[0].value.contacts[0].profile.username | The user’s WhatsApp username. Present when the user has one. | String | No | | entry[0].changes[0].value.messages[0].from | The WhatsApp ID for the customer who replied to an inbound message | String | Yes | | entry[0].changes[0].value.messages[0].id | The message ID for the sent message for an inbound reply | String | Yes | | entry[0].changes[0].value.messages[0].text.body | User’s reply | String | Yes | | key | Message tracking key, created by Insider One. | String | Yes | > [!NOTE] > The `wa_id` field can be empty for a user who adopted a username and did not share their phone number. > [!NOTE] > Key your sessions on both `wa_id` and `user_id`. When `wa_id` is empty, reply with `recipient` set to the `user_id` value. ```json {  "key": "whatsapp-******************", // be empty  "object": "whatsapp_business_account",  "entry": [    {      "id": "1231313123",      "changes": [        {          "value": {            "messaging_product": "whatsapp",            "metadata": {              "display_phone_number": "{{PHONE_NUMBER}}",              "phone_number_id": "{{PHONE_NUMBER_ID}}"            },            "contacts": [              {                "profile": {                  "name": "Insider",                  "username": "{{USERNAME}}"                },                "wa_id": "{{PHONE_NUMBER}}",                "user_id": "{{BSUID}}"              }            ],            "messages": [              {                "from": "{{PHONE_NUMBER}}",                "id": "{{MESSAGE_ID}}",                "timestamp": "111111111",                "text": {                  "body": "{{MESSAGE_BODY}}"                },                "type": "text"              }            ]          },          "field": "messages"        }      ]    }  ] } ``` ### User Reply With Sticker Example ```json {  "object": "whatsapp_business_account",  "entry": [    {      "id": "105710252456442",      "changes": [        {          "value": {            "messaging_product": "whatsapp",            "metadata": {              "display_phone_number": "{{PHONE_NUMBER}}",              "phone_number_id": "{{PHONE_NUMBER_ID}}"            },            "contacts": [              {                "profile": {                  "name": "Insider",                  "username": "{{USERNAME}}"                },                "wa_id": "{{PHONE_NUMBER}}",                "user_id": "{{BSUID}}"              }            ],            "messages": [              {                "from": "393336188770",                "id": "{{MESSAGE_ID}}",                "timestamp": "1678889298",                "type": "sticker",                "sticker": {                  "mime_type": "image/webp",                  "sha256": "{{ENCODED_STICKER}}",                  "id": "{{STICKER_ID}}",                  "animated": true                }              }            ]          },          "field": "messages"        }      ]    }  ] } ``` ### User Reply With Video Example ```json {  "object": "whatsapp_business_account",  "entry": [    {      "id": "105710252456442",      "changes": [        {          "value": {            "messaging_product": "whatsapp",            "metadata": {              "display_phone_number": "{{PHONE_NUMBER}}",              "phone_number_id": "{{PHONE_NUMBER_ID}}"            },            "contacts": [              {                "profile": {                  "name": "Insider",                  "username": "{{USERNAME}}"                },                "wa_id": "{{PHONE_NUMBER}}",                "user_id": "{{BSUID}}"              }            ],            "messages": [              {                "from": "393336188770",                "id": "{{MESSAGE_ID}}",                "timestamp": "1678889564",                "type": "video",                "video": {                  "mime_type": "video/mp4",                  "sha256": "{{ENCODED_VIDEO}}",                  "id": "{{VIDEO_ID}}"                }              }            ]          },          "field": "messages"        }      ]    }  ] } ``` ### User Reply With Image Example ```json {  "object": "whatsapp_business_account",  "entry": [    {      "id": "105710252456442",      "changes": [        {          "value": {            "messaging_product": "whatsapp",            "metadata": {              "display_phone_number": "{{PHONE_NUMBER}}",              "phone_number_id": "{{PHONE_NUMBER_ID}}"            },            "contacts": [              {                "profile": {                  "name": "Insider",                  "username": "{{USERNAME}}"                },                "wa_id": "{{PHONE_NUMBER}}",                "user_id": "{{BSUID}}"              }            ],            "messages": [              {                "from": "393336188770",                "id": "{{MESSAGE_ID}}",                "timestamp": "1678889571",                "type": "image",                "image": {                  "mime_type": "image/jpeg",                  "sha256": "{{ENCODED_IMAGE}}",                  "id": "{{IMAGE_ID}}"                }              }            ]          },          "field": "messages"        }      ]    }  ] } ``` ### User Reply With Document Example ```json {  "object": "whatsapp_business_account",  "entry": [    {      "id": "105710252456442",      "changes": [        {          "value": {            "messaging_product": "whatsapp",            "metadata": {              "display_phone_number": "{{PHONE_NUMBER}}",              "phone_number_id": "{{PHONE_NUMBER_ID}}"            },            "contacts": [              {                "profile": {                  "name": "Insider",                  "username": "{{USERNAME}}"                },                "wa_id": "{{PHONE_NUMBER}}",                "user_id": "{{BSUID}}"              }            ],            "messages": [              {                "from": "393336188770",                "id": "{{MESSAGE_ID}}",                "timestamp": "1678889577",                "type": "document",                "document": {                  "filename": "doc.pdf",                  "mime_type": "application/pdf",                  "sha256": "{{ENCODED_DOCUMENT}}",                  "id": "{{DOCUMENT_ID}}"                }              }            ]          },          "field": "messages"        }      ]    }  ] } ``` ## User Reply with Button Event | Parameter | Description | Data Type | Required | | --- | --- | --- | --- | | entry[0].changes[0].value.metadata.display_phone_number | The phone number that is displayed for a business | String | Yes | | entry[0].changes[0].value.metadata.display_phone_number_id | ID for the phone number. A business can respond to a message using this ID. | String | Yes | | entry[0].changes[0].value.contacts[0].wa_id | The user’s phone number. This value can be empty for a user who adopted a username and did not share their phone number. | String | Yes | | entry[0].changes[0].value.contacts[0].user_id | The user’s BSUID (Business-Scoped User ID). | String | Yes | | entry[0].changes[0].value.contacts[0].profile.name | The user’s WhatsApp display name. | String | Yes | | entry[0].changes[0].value.contacts[0].profile.username | The user’s WhatsApp username. Present when the user has one. | String | No | | entry[0].changes[0].value.messages[0].from | The WhatsApp ID for the customer who replied to an inbound message | String | Yes | | entry[0].changes[0].value.messages[0].id | The message ID for the sent message for an inbound reply | String | Yes | | entry[0].changes[0].value.messages[0].button.body | Button text that is returned when the button is clicked, in addition to the display text on the button | String | Yes | | entry[0].changes[0].value.messages[0].button.payload | A developer-defined payload that is returned when the button is clicked, in addition to the display text on the button | String | Yes | | key | Message tracking key, created by Insider One | String | Yes | ```json {  "key": "whatsapp-**************",  "object": "whatsapp_business_account",  "entry": [    {      "id": "1231313123",      "changes": [        {          "value": {            "messaging_product": "whatsapp",            "metadata": {              "display_phone_number": "{{PHONE_NUMBER}}",              "phone_number_id": "{{PHONE_NUMBER_ID}}"            },            "contacts": [              {                "profile": {                  "name": "Insider",                  "username": "{{USERNAME}}"                },                "wa_id": "{{PHONE_NUMBER}}",                "user_id": "{{BSUID}}"              }            ],            "messages": [              {                "context": {                  "from": "{{PHONE_NUMBER}}",                  "id": "MESSAGE_ID"                },                "from": "{{PHONE_NUMBER}}",                "id": "{{MESSAGE_ID}}",                "timestamp": "1111111",                "type": "button",                "button": {                  "payload": "{{BUTTON_PAYLOAD}}",                  "text": "{{BUTTON_TEXT}}"                }              }            ]          },          "field": "messages"        }      ]    }  ] } ``` ## User Reply with reaction event When a user reacts to a message with an emoji, Insider One sends a message with `type` set to `reaction` to your webhook. The message includes a `reaction` object that identifies the message the user reacted to and the emoji the user selected. The event uses the same format as the other user reply events, except for the message type and the `reaction` object. | Parameter | Description | Data Type | Required | | --- | --- | --- | --- | | entry[0].changes[0].value.messages[0].reaction.message_id | The WhatsApp message ID (wamid) of the message the user reacted to. | String | Yes | | entry[0].changes[0].value.messages[0].reaction.emoji | The emoji the user reacted with. This field is not included when the user removes the reaction. | String | No | > [!NOTE] > For the other fields in the event, refer to [User Reply with Text Event](https://academy.insiderone.com/docs/webhooks-for-conversational-whatsapp#user-reply-with-text-event). ### Reaction added example The following example shows the event when a user adds a reaction. ```json {  "key": "whatsapp-******************",  "object": "whatsapp_business_account",  "entry": [    {      "id": "1231313123",      "changes": [        {          "value": {            "messaging_product": "whatsapp",            "metadata": {              "display_phone_number": "{{PHONE_NUMBER}}",              "phone_number_id": "{{PHONE_NUMBER_ID}}"            },            "contacts": [              {                "profile": {                  "name": "Insider",                  "username": "{{USERNAME}}"                },                "wa_id": "{{PHONE_NUMBER}}",                "user_id": "{{BSUID}}"              }            ],            "messages": [              {                "from": "{{PHONE_NUMBER}}",                "id": "{{MESSAGE_ID}}",                "timestamp": "1111111111",                "type": "reaction",                "reaction": {                  "message_id": "wamid.HBgMOTA1MzIxMjM0NTY3FQIAERgSQjA4RjM1RkQ2QzE4RDg5QjA1AA==",                  "emoji": "👍"                }              }            ]          },          "field": "messages"        }      ]    }  ] } ``` ### Reaction removed example When a user removes a reaction, the `emoji` field is not included. The `message_id` field still identifies the message the user had reacted to. ```json {  "key": "whatsapp-******************",  "object": "whatsapp_business_account",  "entry": [    {      "id": "1231313123",      "changes": [        {          "value": {            "messaging_product": "whatsapp",            "metadata": {              "display_phone_number": "{{PHONE_NUMBER}}",              "phone_number_id": "{{PHONE_NUMBER_ID}}"            },            "contacts": [              {                "profile": {                  "name": "Insider",                  "username": "{{USERNAME}}"                },                "wa_id": "{{PHONE_NUMBER}}",                "user_id": "{{BSUID}}"              }            ],            "messages": [              {                "from": "{{PHONE_NUMBER}}",                "id": "{{MESSAGE_ID}}",                "timestamp": "1111111111",                "type": "reaction",                "reaction": {                  "message_id": "wamid.HBgMOTA1MzIxMjM0NTY3FQIAERgSQjA4RjM1RkQ2QzE4RDg5QjA1AA=="                }              }            ]          },          "field": "messages"        }      ]    }  ] } ``` ## User Reply with Contacts Event When a user shares one or more contact cards, Insider One sends a message with `type` set to `contacts` to your webhook. The message includes a `contacts` array that contains the shared contact cards. Insider One forwards the array exactly as WhatsApp sends it, in the original order, including any fields WhatsApp adds later. The `contacts` array inside `messages[0]` is different from the `contacts` array at the top level of the event. The top-level array identifies the sender and includes `wa_id`, `user_id` and `profile.name`. The array inside `messages[0]` contains the contact cards the user shared. | Parameter | Description | Data Type | Required | | --- | --- | --- | --- | | entry[0].changes[0].value.messages[0].contacts | The contact cards the user shared, in the order they were shared. A message can include more than one contact card. | Array | Yes | | entry[0].changes[0].value.messages[0].contacts[].name | The name of the contact. It can include `formatted_name`, `first_name` and `last_name`. | Object | Optional | | entry[0].changes[0].value.messages[0].contacts[].phones | The phone numbers of the contact. Each item can include `phone`, `wa_id` and `type`. | Array | Optional | | entry[0].changes[0].value.messages[0].contacts[].emails | The email addresses of the contact. Each item can include `email` and `type`. | Array | Optional | | entry[0].changes[0].value.messages[0].contacts[].urls | The websites of the contact. Each item can include `url` and `type`. | Array | Optional | | entry[0].changes[0].value.messages[0].contacts[].addresses | The addresses of the contact. Each item can include `street`, `city`, `country`, `country_code` and `type`. | Array | Optional | | entry[0].changes[0].value.messages[0].contacts[].org | The company of the contact. It can include `company` and `title`. | Object | Optional | | entry[0].changes[0].value.messages[0].contacts[].birthday | The birthday of the contact, in `YYYY-MM-DD` format. | String | Optional | Insider One forwards the contact cards exactly as Meta sends them. The fields in each card depend on the card the user shares. > [!NOTE] > For the other fields in the event, refer to [User Reply with Text Event](https://academy.insiderone.com/docs/webhooks-for-conversational-whatsapp#user-reply-with-text-event). ### User Reply with Contacts Example The following example shows the event when a user shares a contact card. ```json {  "key": "whatsapp-******************",  "object": "whatsapp_business_account",  "entry": [    {      "id": "1231313123",      "changes": [        {          "value": {            "messaging_product": "whatsapp",            "metadata": {              "display_phone_number": "{{PHONE_NUMBER}}",              "phone_number_id": "{{PHONE_NUMBER_ID}}"            },            "contacts": [              {                "profile": {                  "name": "Insider",                  "username": "{{USERNAME}}"                },                "wa_id": "{{PHONE_NUMBER}}",                "user_id": "{{BSUID}}"              }            ],            "messages": [              {                "from": "{{PHONE_NUMBER}}",                "id": "{{MESSAGE_ID}}",                "timestamp": "1111111111",                "type": "contacts",                "contacts": [                  {                    "name": {                      "formatted_name": "Jane Doe",                      "first_name": "Jane",                      "last_name": "Doe"                    },                    "phones": [                      {                        "phone": "+90 555 123 4567",                        "wa_id": "905551234567",                        "type": "CELL"                      }                    ],                    "emails": [                      {                        "email": "jane@example.com",                        "type": "WORK"                      }                    ],                    "urls": [                      {                        "url": "https://example.com",                        "type": "WORK"                      }                    ],                    "addresses": [                      {                        "street": "1 Example St",                        "city": "Istanbul",                        "country": "Turkey",                        "country_code": "TR",                        "type": "WORK"                      }                    ],                    "org": {                      "company": "Example Inc.",                      "title": "Manager"                    },                    "birthday": "1990-01-31"                  }                ]              }            ]          },          "field": "messages"        }      ]    }  ] } ``` ## BSUID change events A user’s BSUID can change. When it does, Insider One forwards a system message to your webhook. The message has `type: "system"` and one of these `system.type` values: - `user_changed_user_id`: The BSUID changed on its own. - `user_changed_number`: The user changed their phone number, which also creates a new BSUID. The old BSUID is in `system.previous_user_id`, and the new BSUID is in `contacts[].user_id`. Move the state you store from the old BSUID to the new one. Insider One updates the user profile itself. | Parameter | Description | Data Type | Required | | --- | --- | --- | --- | | entry[0].changes[0].value.contacts[0].wa_id | The user’s phone number. | String | Yes | | entry[0].changes[0].value.contacts[0].user_id | The user’s new BSUID. | String | Yes | | entry[0].changes[0].value.messages[0].type | system | String | Yes | | entry[0].changes[0].value.messages[0].system.type | The reason for the change: `user_changed_user_id` or `user_changed_number`. | String | Yes | | entry[0].changes[0].value.messages[0].system.previous_user_id | The user’s old BSUID. | String | Yes | The following sample shows a `user_changed_user_id` event. ```json {  "entry": [    {      "changes": [        {          "field": "messages",          "value": {            "contacts": [              {                "wa_id": "905551112233",                "user_id": "TR.NEW123",                "profile": {                  "name": "Jane"                }              }            ],            "messages": [              {                "id": "wamid.XXX",                "timestamp": "1755072000",                "type": "system",                "system": {                  "type": "user_changed_user_id",                  "previous_user_id": "TR.OLD123"                }              }            ]          }        }      ]    }  ] } ``` ## Manage Multiple WhatsApp Webhooks with OAuth 2.0 You can use the V2 WhatsApp Webhook API to list, create, update, and delete OAuth 2.0-authenticated webhooks for a sender. This version is designed for integrations that want Insider One to authenticate outbound webhook delivery by using OAuth 2.0 credentials. > All examples below use a dummy sender phone number `15551234567`. Replace it with your own sender number when you go live. > [!WARNING] > **Important** > > The OAuth token generation request uses **snake_case** fields such as `client_id` and `client_secret`. The webhook CRUD request bodies use **camelCase** fields such as `authType`, `webhookUrl`, `grantType`, and `clientId`. ### Required OAuth 2.0 Scopes | Scope | Description | | --- | --- | | wa-webhook-read | Allows you to list OAuth 2.0 webhooks and inspect their details. | | wa-webhook-manage | Allows you to create new OAuth 2.0 webhooks. | | wa-webhook-update | Allows you to update existing OAuth 2.0 webhooks. | | wa-webhook-delete | Allows you to delete existing OAuth 2.0 webhooks. | ### Generate an OAuth Access Token Before calling the V2 webhook endpoints, generate a **Gateway access token**. Notice that this request uses **snake_case** field names. ```bash curl --location 'https://gw.useinsider.com/auth/token' \ --header 'Content-Type: application/json' \ --data '{    "client_id": "your-client-id",    "client_secret": "your-client-secret",    "scopes": ["wa-webhook-read", "wa-webhook-manage", "wa-webhook-update", "wa-webhook-delete"] }' ``` ### Endpoint Overview | Operation | Method | Endpoint | Description | | --- | --- | --- | --- | | List OAuth 2.0 webhooks | GET | https://gw.useinsider.com/api/wa/v2/webhooks?phoneNumber=15551234567 | Returns only OAuth 2.0 webhook records for the sender. | | Create OAuth 2.0 webhook | POST | https://gw.useinsider.com/api/wa/v2/webhooks?phoneNumber=15551234567 | Creates a new OAuth 2.0 webhook. | | Update OAuth 2.0 webhook | PUT | https://gw.useinsider.com/api/wa/v2/webhooks/84?phoneNumber=15551234567 | Updates an existing OAuth 2.0 webhook. | | Delete OAuth 2.0 webhook | DELETE | https://gw.useinsider.com/api/wa/v2/webhooks/84?phoneNumber=15551234567 | Soft deletes the selected OAuth 2.0 webhook. | ### Authentication All V2 webhook CRUD requests must include a bearer token returned by the Gateway Auth API. - Authorization: Bearer your-oauth2-token ### Field Naming Reference | Flow | Example Fields | Expected Style | | --- | --- | --- | | Token generation request | `client_id`, `client_secret`, `scopes` | snake_case | | Webhook CRUD request body | `authType`, `subProduct`, `webhookUrl`, `grantType`, `tokenUrl`, `clientId`, `clientSecret`, `refreshToken`, `expiresIn` | camelCase | ### Create Webhook Request Body | Field | Type | Required | Description | | --- | --- | --- | --- | | authType | String | Yes | Must be `oauth2`. | | subProduct | String | Yes | Accepted values are `transactional` and `conversational`. | | webhookUrl | String | Yes | The destination endpoint that will receive webhook events. | | events | Array[String] | Yes | Supported values are `sent`, `delivered`, `read`, `reply`, and `failed`. | | oauth2.grantType | String | Yes | Accepted values are `client_credentials` and `refresh_token`. | | oauth2.tokenUrl | String | Yes | Your token endpoint used to fetch an access token for outbound delivery. | | oauth2.clientId | String | Yes | The OAuth 2.0 client identifier used for outbound webhook delivery. | | oauth2.clientSecret | String | Yes | The client secret used for outbound webhook delivery. | | oauth2.refreshToken | String | Required for `refresh_token` | Only required when `grantType` is `refresh_token`. | | oauth2.scopes | Array[String] | No | The scopes Insider should send to your OAuth 2.0 token endpoint. | | oauth2.expiresIn | Integer | Yes | The token lifetime in seconds. | > [!WARNING] > **Important:** If **subProduc**t is conversational, the **reply** event must be included in the events array. ### List Webhooks ```bash curl --location 'https://gw.useinsider.com/api/wa/v2/webhooks?phoneNumber=15551234567' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer your-oauth2-token' ``` ### Sample Response ```json {  "webhooks": [    {      "id": 84,      "subProduct": "conversational",      "webhookUrl": "https://partner.example.com/whatsapp/webhooks/conversational",      "events": [        "reply",        "read"      ],      "isActive": true,      "authType": "oauth2",      "oauth2": {        "grantType": "client_credentials",        "tokenUrl": "https://partner.example.com/oauth/token",        "clientId": "partner-whatsapp-client",        "clientSecret": "partner-whatsa********",        "scopes": [          "whatsapp.read",          "whatsapp.write"        ],        "expiresIn": 3600      },      "createdAt": "2026-04-15T12:45:00Z"    }  ] } ``` ### Create Webhook This example uses the `client_credentials` grant type. Notice that the CRUD body uses **camelCase** field names. ```bash curl --location 'https://gw.useinsider.com/api/wa/v2/webhooks?phoneNumber=15551234567' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer your-oauth2-token' \ --data '{  "authType": "oauth2",  "subProduct": "conversational",  "webhookUrl": "https://partner.example.com/whatsapp/webhooks/conversational",  "events": ["sent", "delivered", "failed"],  "oauth2": {    "grantType": "client_credentials",    "tokenUrl": "https://partner.example.com/oauth/token",    "clientId": "partner-whatsapp-client",    "clientSecret": "partner-whatsapp-secret",    "scopes": ["whatsapp.read", "whatsapp.write"],    "expiresIn": 3600  } }' ``` ### Sample Response ```json {  "status": true,  "webhook": 84 } ``` ### Create a Webhook with refresh_token If your OAuth 2.0 server requires the `refresh_token` grant type, include `refreshToken` in the request body. ```json {  "authType": "oauth2",  "subProduct": "conversational",  "webhookUrl": "https://partner.example.com/whatsapp/webhooks/conversational",  "events": ["sent", "delivered", "failed"],  "oauth2": {    "grantType": "refresh_token",    "tokenUrl": "https://partner.example.com/oauth/token",    "clientId": "partner-whatsapp-client",    "clientSecret": "partner-whatsapp-secret",    "refreshToken": "partner-refresh-token",    "scopes": ["whatsapp.read", "whatsapp.write"],    "expiresIn": 3600  } } ``` ### Update Webhook Do not send `subProduct` in the update body. Keep using camelCase field names for all nested OAuth 2.0 fields. ```bash curl --location --request PUT 'https://gw.useinsider.com/api/wa/v2/webhooks/84?phoneNumber=15551234567' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer your-oauth2-token' \ --data '{  "authType": "oauth2",  "webhookUrl": "https://partner.example.com/whatsapp/webhooks/conversational-v2",  "events": ["sent", "delivered", "read", "failed"],  "oauth2": {    "grantType": "client_credentials",    "tokenUrl": "https://partner.example.com/oauth/token",    "clientId": "partner-whatsapp-client",    "clientSecret": "partner-whatsapp-secret-updated",    "scopes": ["whatsapp.read", "whatsapp.write"],    "expiresIn": 3600  } }' ``` ### Sample Response ```json {  "status": true,  "webhook": 84 } ``` ### Delete Webhook ```bash curl --location --request DELETE 'https://gw.useinsider.com/api/wa/v2/webhooks/84?phoneNumber=15551234567' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer your-oauth2-token' ``` ### Sample Response ```json {  "status": true } ``` ### Error Responses | Scenario | Status Code | Sample Message | | --- | --- | --- | | The sender's phone number is missing. | 400 | `phoneNumber query parameter is required` | | The request body uses an unsupported auth type. | 400 | `V2 API only supports OAuth2 authentication. Use V1 API for bearer token.` | | The webhook URL already exists for the selected API type. | 400 | `This webhook URL already exists for the selected API type` | | The sender cannot be matched to a provider. | 404 | `Provider not found for the given phone number` | ### Best Practices - Keep snake_case only for the token generation request and camelCase for the webhook CRUD request bodies. - Store the returned webhook ID so you can update or delete the same record later. - Use dedicated OAuth 2.0 credentials for each environment whenever possible. - Validate the sender number and webhook URL values before moving your integration to production. - Use the V2 endpoints with OAuth 2.0 authentication. Insider One fetches and refreshes the access token from your token endpoint, so token rotation on your side does not interrupt webhook delivery. V1 endpoints send a static bearer token, which causes 401 or 403 errors after a rotation until you update it.