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

# Webhooks

> Send real-time notifications of Quo, formerly OpenPhone, events to your applications with secure, customizable webhooks

## Overview

Webhooks provide real-time notifications when events occur in your Quo workspace, enabling powerful integrations and automated workflows. Configure webhooks to send event data to your applications instantly when calls complete, messages arrive, or contacts change.

## How webhooks work

**Event-driven notifications:**
Webhooks deliver instant notifications when specific events occur in your Quo workspace. When an event triggers, Quo sends an HTTP POST request to your specified URL with detailed event data.

**Typical workflow:**

1. Configure webhook URL and select event types
2. Quo monitors for specified events
3. When event occurs, Quo sends POST request with event payload
4. Your application processes the event data and responds
5. Quo logs the delivery status and retries if needed

<Note>
  Webhook configuration requires workspace Owner or Admin permissions. Settings are managed through web and desktop apps only.
</Note>

**Webhook configuration interface:**

<Frame>
  <img src="https://mintcdn.com/resource-center/0h8Ltw2udwKi-9vO/images/core-concepts-apis-integrations/how-to-use-webhooks_webhookspng_d0d29d48.png?fit=max&auto=format&n=0h8Ltw2udwKi-9vO&q=85&s=acf42dc8b745e5f27aebc8d770a288f6" alt="Webhook creation form in Quo settings" style={{ maxHeight: '450px' }} width="1500" height="892" data-path="images/core-concepts-apis-integrations/how-to-use-webhooks_webhookspng_d0d29d48.png" />
</Frame>

## Available webhook events

### Messaging events

**Text message notifications:**

* **`message.received`**: Text message received by workspace phone number (includes media attachments)
* **`message.delivered`**: Text message sent from workspace and successfully delivered (includes media)

**AI-powered insights:**

* **`call.summary.completed`**: AI-generated call summary available in event payload
* **`call.transcript.completed`**: Complete call transcript available in event payload

### Voice events

**Call status notifications:**

* **`call.ringing`**: Incoming call being received by workspace phone number
* **`call.completed`**: Call finished (answered or unanswered, may include voicemail)
* **`call.recording.completed`**: Call recording available at provided URL

### Contact events

**Contact management:**

* **`contact.updated`**: Contact created or modified in workspace
* **`contact.deleted`**: Contact removed from workspace

## Setting up webhooks

### Configuration requirements

**Required parameters:**

| Parameter       | Requirement | Description                                                                 |
| --------------- | ----------- | --------------------------------------------------------------------------- |
| **URL**         | Required    | Webhook handler endpoint (HTTPS strongly recommended for production)        |
| **Event types** | Required    | Select one or more event types to monitor                                   |
| **Resources**   | Required    | Choose phone numbers (calls/messages) or users/groups (contacts) to monitor |

**Optional parameters:**

| Parameter | Description                                                |
| --------- | ---------------------------------------------------------- |
| **Label** | Descriptive name for webhook identification and management |

### Setup process

**To create a webhook:**

1. Navigate to **Settings** → **Webhooks** in Quo
2. Click **Create webhook**
3. Enter your webhook handler URL
4. Select event types to monitor
5. Choose phone numbers or contact resources
6. Add optional label for identification
7. Save and test configuration

## Building webhook handlers

### Handler requirements

**Technical specifications:**

* Accept HTTP POST requests at your webhook URL
* Process JSON event payload in request body
* Respond with 2xx HTTP status code within 10 seconds
* Verify webhook signature for security
* Handle retries and failures gracefully

**Response handling:**

* **Success**: Return 2xx status code (no response body required)
* **Failure**: Non-2xx response triggers Quo retry sequence
* **Timeout**: No response within 10 seconds initiates retries

**Development flexibility:**
Webhook handlers can be built in any programming language that supports HTTP requests and responses. Deploy to cloud platforms, servers, or serverless functions.

### Security and authentication

**Webhook signature verification:**
All webhook calls include cryptographic signatures to verify authenticity and prevent spoofing attacks.

**Signature header format:**

```
'openphone-signature': 'hmac;1;1639710054089;mw1K4fvh5m9XzsGon4C5N3KvL0bkmPZSAy
b/9Vms2Qo='
```

**Signature structure:**

```
<scheme>;<version>;<timestamp>;<signature>
```

| Component     | Description                   | Current Value  |
| ------------- | ----------------------------- | -------------- |
| **scheme**    | Signature algorithm           | Always "hmac"  |
| **version**   | Signature version             | Always "1"     |
| **timestamp** | Signature generation time     | Unix timestamp |
| **signature** | Base64 encoded HMAC signature | SHA256 digest  |

### Signature verification process

**Verification steps:**

1. **Extract components** from `openphone-signature` header
2. **Prepare signed data** by concatenating `timestamp + "." + payload`
3. **Decode signing key** from base64 (available in webhook details)
4. **Compute HMAC-SHA256** using decoded key and signed data
5. **Compare result** with signature from header

**Important requirements:**

* Remove all whitespace and newlines from JSON payload before concatenation
* Use binary form of base64-decoded signing key for HMAC computation
* Ensure exact string matching for verification success

**Getting your signing key:**

1. Go to webhook details page in Quo
2. Click ellipses (⋯) at top right
3. Select "Reveal signing secret"
4. Copy base64-encoded key for your application

### Implementation examples

**Node.js webhook handler:**

```javascript theme={null}
const express = require("express")
const bodyParser = require('body-parser')
const crypto = require('node:crypto');

const app = express()
const port = 8000

app.use(bodyParser.json())

app.post("/", function (req, res) {
  // signingKey is from "Reveal Signing Secret" in the Quo app.
  const signingKey = 'R2ZLM2o0bFhBNVpyUnU2NG9mYXQ1MHNyR3pvSUhIVVg='

  // Parse the fields from the openphone-signature header.
  const signature = req.headers['openphone-signature']
  const fields = signature.split(';')
  const timestamp = fields[2]
  const providedDigest = fields[3]

  // Compute the data covered by the signature.
  const signedData = timestamp + '.' + JSON.stringify(req.body)

  // Convert the base64-encoded signing key to binary.
  const signingKeyBinary = Buffer.from(signingKey, 'base64').toString('binary')

  // Compute the SHA256 HMAC digest.
  const computedDigest = crypto.createHmac('sha256',signingKeyBinary)
    .update(Buffer.from(signedData,'utf8'))
    .digest('base64')

  // Verify signature matches
  if (providedDigest === computedDigest) {
    console.log(`signature verification succeeded`)
    // Process webhook event here
  } else {
    console.log(`signature verification failed`)
    return res.status(401).send('Unauthorized')
  }

  res.send({})
});

app.listen(port, function () {
  console.log(`webhook server listening on port ${port}`)
})
```

**Python webhook handler:**

```python theme={null}
import base64
import hmac
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/', methods=['POST'])
def handle_webhook_call():
    # signingKey is from "Reveal Signing Secret" in the Quo app.
    signing_key = 'R2ZLM2o0bFhBNVpyUnU2NG9mYXQ1MHNyR3pvSUhIVVg='

    # Parse the fields from the openphone-signature header.
    signature = request.headers['openphone-signature']
    fields = signature.split(';')
    timestamp = fields[2]
    provided_digest = fields[3]

    # Compute the data covered by the signature as bytes.
    signed_data_bytes = b''.join([timestamp.encode(), b'.', request.data])

    # Convert the base64-encoded signing key to bytes.
    signing_key_bytes = base64.b64decode(signing_key)

    # Compute the SHA256 HMAC digest.
    hmac_object = hmac.new(signing_key_bytes, signed_data_bytes, 'sha256')
    computed_digest = base64.b64encode(hmac_object.digest()).decode()

    # Verify signature matches
    if provided_digest == computed_digest:
        print('signature verification succeeded')
        # Process webhook event here
    else:
        print('signature verification failed')
        return jsonify({'error': 'Unauthorized'}), 401
        
    return jsonify({})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=True)
```

<Note>
  Future versions may include multiple signatures separated by commas. Split the header value on commas to handle multiple signatures if needed.
</Note>

### Security best practices

**Replay attack protection:**
Implement timestamp validation to prevent replay attacks:

* Compare signature timestamp with current time
* Reject requests with timestamps outside acceptable tolerance (e.g., 5 minutes)
* Each webhook call generates unique timestamp and signature
* Retries automatically include new timestamps

**Additional security measures:**

* Always use HTTPS URLs for production webhooks
* Store signing keys securely (environment variables, secret management)
* Implement proper error handling and logging
* Consider rate limiting for webhook endpoints

## Error handling and retries

### Automatic retry system

**Retry behavior:**

* **Trigger conditions**: Non-2xx response codes or 10-second timeout
* **Backoff strategy**: Exponential backoff with increasing delays
* **Retry duration**: Up to 3 days of retry attempts
* **Final failure**: Email notification sent to webhook creator

**Retry timeline:**

* Initial retries happen quickly for minimal delay
* Delays increase exponentially with each attempt
* Prioritizes delivery close to original event time
* Automatic status tracking throughout process

### Manual retry options

**Webhook management:**

* View delivery status in Quo webhook details
* Manually retry failed webhook calls anytime
* Failed webhooks marked with 'failure' status
* Successful manual retries update status to 'success'

### Failure notifications

**When retries exhaust:**

* Email alert sent to webhook creator
* Webhook call marked as permanently failed
* Event details preserved for manual review
* Option to retry manually when issues resolved

## Testing and validation

### Development testing

**Local testing options:**

* **HTTP clients**: Use cURL, Postman, or Insomnia to send test POST requests
* **Local URLs**: Test against localhost during development
* **Mock payloads**: Create sample event JSON matching Quo format
* **Signature testing**: Verify HMAC validation logic with test keys

### Quo test features

**Built-in test request:**

1. Navigate to webhook details page in Quo
2. Click ellipses (⋯) at top right
3. Select "Send Test Request"
4. Quo sends sample event to your webhook URL
5. Verify signature verification and response handling

<Note>
  Test requests require publicly accessible webhook URLs. Local development URLs won't work with Quo's test feature.
</Note>

### Live event testing

**Real event triggers:**

* Configure webhook for specific event type (e.g., `message.received`)
* Select your Quo number in webhook resources
* Trigger actual event (send text to your number)
* Monitor webhook delivery and processing
* Verify complete end-to-end functionality

**Testing checklist:**

* \[ ] Webhook receives POST requests correctly
* \[ ] Signature verification works
* \[ ] Event payload parsing succeeds
* \[ ] Application logic processes events properly
* \[ ] Error responses trigger retries
* \[ ] Success responses stop retry sequence

## Troubleshooting

### Common issues and solutions

**Configuration problems:**

* **Verify webhook URL** is correct and accessible
* **Check webhook status** is enabled in settings
* **Confirm event types** are properly selected
* **Validate resources** (phone numbers/users/groups) are correct

**Response issues:**

* **HTTP status codes**: Ensure 2xx responses for successful processing
* **Response timing**: Return responses within 10-second timeout
* **Error handling**: Implement proper error responses for debugging

**Security verification:**

* **Signature validation**: Verify HMAC signature computation
* **Key format**: Ensure signing key is properly base64 decoded
* **Timestamp handling**: Check timestamp extraction and concatenation

### Debugging tools

**Event monitoring:**

* **Events log**: View delivery history in webhook details page
* **Status tracking**: Monitor success/failure rates
* **Retry attempts**: Review automatic retry sequences
* **Manual testing**: Use "Send Test Request" for immediate validation

**Performance optimization:**

* **Response time**: Keep processing under 10 seconds
* **Error rates**: Minimize failures to reduce retry overhead
* **Logging**: Implement comprehensive request/response logging
* **Monitoring**: Set up alerts for webhook delivery failures

## Event payload examples

### Message events

**`message.received` payload:**

```json theme={null}
{  
  "id": "EVc67ec998b35c41d388af50799aeeba3e",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-23T16:55:52.557Z",  
  "type": "message.received",  
  "data": {  
    "object": {  
      "id": "AC24a8b8321c4f4cf2be110f4250793d51",  
      "object": "message",  
      "from": "+14155550100",  
      "to": "+13105550199",  
      "direction": "incoming",  
      "body": "Hello",  
      "media": [  
        {  
         "url": "https://storage.googleapis.com/opstatics-dev/6c908000ada94d9fb206649ecb8cc928",  
         "type": "image/jpeg"  
        }  
      ],  
      "status": "received",  
      "createdAt": "2022-01-23T16:55:52.420Z",  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}
```

**`message.delivered` payload:**

```json theme={null}
{  
  "id": "EVdefd85c2c3b740429cf28ade5b69bcba",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-23T17:05:56.220Z",  
  "type": "message.delivered",  
  "data": {  
    "object": {  
      "id": "ACcdcc2668c4134c3cbfdacb9e273cac6f",  
      "object": "message",  
      "from": "+13105550199",  
      "to": "+14155550100",  
      "direction": "outgoing",  
      "body": "Have a nice day",  
      "media": [  
        {  
         "url": "https://opstatics-dev.s3.amazonaws.com/i/ab6084db-5259-42c0-93c1-e17fb2628567.jpeg",  
         "type": "image/jpeg"  
        }  
      ],  
      "status": "delivered",  
      "createdAt": "2022-01-23T17:05:45.195Z",  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}
```

### Call events

**`call.ringing` payload:**

```json theme={null}
{  
  "id": "EV95c3708f9112412a834cc8d415470cd8",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-23T17:07:51.454Z",  
  "type": "call.ringing",  
  "data": {  
    "object": {  
      "id": "ACbaee66e137f0467dbed5ad4bc8d60800",  
      "object": "call",  
      "from": "+14155550100",  
      "to": "+13105550199",  
      "direction": "incoming",  
      "media": [],  
      "voicemail": null,  
      "status": "ringing",  
      "createdAt": "2022-01-23T17:07:51.116Z",  
      "answeredAt": null,  
      "completedAt": null,  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}
```

**`call.completed` (incoming with voicemail):**

```json theme={null}
{  
  "id": "EVd39d3c8d6f244d21a9131de4fc9350d0",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-24T19:22:25.427Z",  
  "type": "call.completed",  
  "data": {  
    "object": {  
      "id": "ACa29ee906a4e04312a6928427b1c21721",  
      "object": "call",  
      "from": "+14145550100",  
      "to": "+13105550199",  
      "direction": "incoming",  
      "media": [],  
      "voicemail": {  
        "url": "https://m.openph.one/static/85ad4740be6048e4a80efb268d347482.mp3",  
        "type": "audio/mpeg",  
        "duration": 7  
      },  
      "status": "completed",  
      "createdAt": "2022-01-24T19:21:59.545Z",  
      "answeredAt": null,  
      "completedAt": "2022-01-24T19:22:19.000Z",  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}
```

**`call.completed` (outgoing answered call):**

```json theme={null}
{  
  "id": "EV348de11e4b134fa48017ac45a251dd3e",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-24T19:28:45.370Z",  
  "type": "call.completed",  
  "data": {  
    "object": {  
      "id": "AC7ab6f57e62924294925d0ea961de7dc5",  
      "object": "call",  
      "from": "+13105550199",  
      "to": "+14155550100",  
      "direction": "outgoing",  
      "media": [],  
      "voicemail": null,  
      "status": "completed",  
      "createdAt": "2022-01-24T19:28:33.892Z",  
      "answeredAt": "2022-01-24T19:28:42.000Z",  
      "completedAt": "2022-01-24T19:28:45.000Z",  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}
```

**`call.recording.completed` payload:**

```json theme={null}
{  
  "id": "EVda6e196255814311aaac1983005fa2d9",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-24T19:30:55.400Z",  
  "type": "call.recording.completed",  
  "data": {  
    "object": {  
      "id": "AC0d3b9011efa041d78c864019ad9e948c",  
      "object": "call",  
      "from": "+14155550100",  
      "to": "+13105550199",  
      "direction": "incoming",  
      "media": [  
        {   
          "url": "https://storage.googleapis.com/opstatics-dev/b5f839bc72a24b33a7fc032f78777146.mp3",  
          "type": "audio/mpeg",  
          "duration": 7  
        }  
      ],  
      "voicemail": null,  
      "status": "completed",  
      "createdAt": "2022-01-24T19:30:34.675Z",  
      "answeredAt": "2022-01-24T19:30:38.000Z",  
      "completedAt": "2022-01-24T19:30:48.000Z",  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}
```

### Contact events

**`contact.updated` and `contact.deleted` payload:**

```json theme={null}
{  
  "id": "EVe844e47e9fa4494d9acfa1144839ed94",  
  "object": "event",  
  "createdAt": "2022-01-24T19:44:09.579Z",  
  "apiVersion": "v2",  
  "type": "contact.updated",  
  "data": {  
    "object": {  
      "id": "CT61eeff33f3b14cfe6358cb52",  
      "object": "contact",  
      "firstName": "Jane",  
      "lastName": "Smith",  
      "company": "Comp Inc",  
      "role": "Agent",  
      "pictureUrl": null,  
      "fields": [  
         {  
           "name": "Phone",  
           "type": "phone-number",  
           "value": "+14155551212"  
         },  
         {  
           "name": "Email",  
           "type": "email",  
           "value": null  
         },  
         {  
           "name": "Prop1",  
           "type": "string",  
           "value": "Value12"  
         }  
      ],  
      "notes": [  
        {  
          "text": "@USu5AsEHuQ mynote 😂",  
          "enrichment": {  
            "taggedIds": {   
              "groupIds": [],   
              "userIds": [  
                "USu5AsEHuQ"  
              ],  
              "orgIds": []  
            },  
            "tokens": {   
              "USu5AsEHuQ": {   
                "token": "USu5AsEHuQ",   
                "replacement": "Tom Smith",   
                "type": "mention",   
                "locations": [  
                  {   
                    "startIndex": 1,   
                    "endIndex": 11   
                  }  
                ]   
              }   
            }   
          },  
          "createdAt": "2022-01-24T19:35:38.323Z",  
          "updatedAt": "2022-01-24T19:35:38.323Z",  
          "userId": "USu5AsEHuQ"  
        }  
      ],  
      "sharedWith": [  
        "USu5AsEHuQ"  
       ],  
      "createdAt": "2022-01-24T19:35:38.318Z",  
      "updatedAt": "2022-01-24T19:44:09.565Z",  
      "userId": "USu5AsEHuQ"  
    }  
  }  
}
```

### AI analysis events

**`call.summary.completed` payload:**

```json theme={null}
{  
  "id": "EVc86d16fed5314cf6bd7bf13f11c65fd2",  
  "object": "event",  
  "apiVersion": "v3",  
  "createdAt": "2024-09-05T15:30:24.213Z",  
  "type": "call.summary.completed",  
  "data": {  
    "object": {  
      "object": "callSummary",  
      "callId": "AC641569d25e0a4489ad807409d9bee3fd",  
      "status": "completed",  
      "summary": [  
        "this is your call summary."  
      ],  
      "nextSteps": [  
        "these are your next steps."  
      ]  
    }  
  }  
}
```

**`call.transcript.completed` payload:**

```json theme={null}
{  
  "id": "EV67e3564088bf4badb6593cd7db8f2832",  
  "object": "event",  
  "apiVersion": "v3",  
  "createdAt": "2024-09-05T15:30:18.629Z",  
  "type": "call.transcript.completed",  
  "data": {  
    "object": {  
      "object": "callTranscript",  
      "callId": "AC741569d25e0a4489ad804709d9bee3fc",  
      "createdAt": "2024-09-05T15:30:18.198Z",  
      "dialogue": [  
        {  
          "end": 73.57498,  
          "start": 0.03998,  
          "userId": "UStpTEr9a6",  
          "content": "hello world",  
          "identifier": "+12345678901"  
        },  
        {  
          "end": 1.52,  
          "start": 1.12,  
          "userId": "USiAYGldYE",  
          "content": "hi there",  
          "identifier": "+19876543210"  
        }  
      ],  
      "duration": 87.614685,  
      "status": "completed"  
    }  
  }  
}
```

## Additional resources

**Need help?** Submit a support request at [support.quo.com](https://support.quo.com/help/submit-a-request) for technical assistance with webhook implementation.
