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

# Connection events

> Know when a customer's connect succeeds or fails.

When a customer finishes an [onboarding link](/saas-mode/onboarding-links), you can find out two ways: the redirect on their browser, and a webhook to your server.

## Redirects

We send the customer's browser back to your redirect URL with query params attached.

### Success

`successRedirectUrl` (or a HookMyApp page if you didn't set one):

| Param                  | Value                                                                        |
| ---------------------- | ---------------------------------------------------------------------------- |
| `status`               | Always `completed`.                                                          |
| `phone_number_id`      | The connected WhatsApp phone number ID. Omitted for Instagram.               |
| `display_phone_number` | The connected WhatsApp number, formatted for display. Omitted for Instagram. |
| `externalId`           | Your external ID for this link, if you set one.                              |

### Failure

`failureRedirectUrl` (or a HookMyApp page if you didn't set one):

| Param        | Value                                           |
| ------------ | ----------------------------------------------- |
| `status`     | Always `failed`.                                |
| `reason`     | Why it failed. See the table below.             |
| `externalId` | Your external ID for this link, if you set one. |

## Reason codes

| Reason                | What happened                                                                | What to do                                                      |
| --------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------- |
| `customer_cancelled`  | Your customer left or declined the Meta connect flow.                        | Resend the link.                                                |
| `permission_denied`   | Your customer didn't grant the access WhatsApp or Instagram needs.           | Ask them to retry and approve every permission.                 |
| `already_connected`   | That number or account is already connected elsewhere.                       | Have them free it up, or connect a different number or account. |
| `no_number_available` | They finished Meta's flow but have no WhatsApp number ready to connect.      | Ask them to finish WhatsApp Business setup, then retry.         |
| `link_inactive`       | The link was revoked, already used, or its target customer no longer exists. | Mint a new link.                                                |
| `temporary_failure`   | A short-lived hiccup: rate limit, expired session, brief pause.              | Ask them to try again in a few minutes.                         |
| `service_error`       | Something went wrong on our side. We're already alerted.                     | Contact support if it keeps happening.                          |

## Webhook events

Set `connectedNotificationUrl` when you create the link to get a server-to-server POST the moment your customer's connect finishes, whether it succeeds or fails.

<Note>
  Events only go to `connectedNotificationUrl`. They are never mixed into your [message destination webhook](/saas-mode/onboarding-links#message-destination), which only carries inbound WhatsApp and Instagram messages.
</Note>

### channel.connected

```json theme={null}
{
  "event": "channel.connected",
  "externalId": "your-id",
  "channelType": "whatsapp",
  "channelPublicId": "ch_XXXXXXXX",
  "workspaceId": "ws_XXXXXXXX",
  "phoneNumberId": "106540000000002",
  "connectedAt": "2026-07-29T12:00:00.000Z"
}
```

`phoneNumberId` is `null` for Instagram.

### channel.connect\_failed

```json theme={null}
{
  "event": "channel.connect_failed",
  "externalId": "your-id",
  "channelType": "whatsapp",
  "reason": "permission_denied",
  "occurredAt": "2026-07-29T12:00:00.000Z"
}
```

`reason` is one of the codes above.

## Headers

Every event POST carries:

| Header                      | Value                                                 |
| --------------------------- | ----------------------------------------------------- |
| `X-HookMyApp-Event`         | `channel.connected` or `channel.connect_failed`       |
| `X-HookMyApp-Signature-256` | `sha256=<hex>`, HMAC-SHA256 over the raw request body |

## Signature verification

Compute HMAC-SHA256 over the raw request body using the signing secret returned when you create, list, or regenerate the link.<br />
This secret is separate from the Verify Token. The Verify Token is only used for the ownership handshake on your [message destination webhook](/saas-mode/onboarding-links#message-destination), never for signing.

```javascript theme={null}
import express from 'express'
import { createHmac, timingSafeEqual } from 'node:crypto'

const app = express()
const SIGNING_SECRET = process.env.CONNECTION_EVENT_SIGNING_SECRET

app.post(
  '/webhooks/hookmyapp-connect',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.get('X-HookMyApp-Signature-256') || ''
    const expected = 'sha256=' +
      createHmac('sha256', SIGNING_SECRET)
        .update(req.body)
        .digest('hex')

    const a = Buffer.from(signature)
    const b = Buffer.from(expected)
    if (a.length !== b.length || !timingSafeEqual(a, b)) {
      return res.sendStatus(401)
    }

    const payload = JSON.parse(req.body.toString('utf8'))
    if (payload.event === 'channel.connected') {
      // Your customer finished connecting.
    } else if (payload.event === 'channel.connect_failed') {
      // Your customer's connect failed. payload.reason explains why.
    }
    res.sendStatus(200)
  },
)
```

## Retries

If your endpoint doesn't answer with a 2xx, we retry up to 8 attempts, waiting longer between each one, from about a minute up to an hour. If every attempt fails, delivery stops.
