> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.agentphone.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.agentphone.ai/_mcp/server.

# Testing

> Test webhooks locally, use mock payloads, and verify your integration

This guide covers how to test your AgentPhone integration during development, including local webhook testing and mock payloads.

## Testing webhooks locally

To receive webhooks during development, you need to expose your local server to the internet. We recommend ngrok.

### Using ngrok

### Install ngrok

Download from [ngrok.com](https://ngrok.com/download), or install via Homebrew:

```bash
brew install ngrok
```

### Start your local server

```bash
python app.py  # or node server.js
```

### Expose your local port

In a separate terminal:

```bash
ngrok http 3000
```

Copy the HTTPS URL (e.g., `https://abc123.ngrok.io`).

### Register the URL as your webhook

```bash
curl -X POST "https://api.agentphone.ai/v1/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://abc123.ngrok.io/webhook"}'
```

### Send a test webhook

```bash
curl -X POST "https://api.agentphone.ai/v1/webhooks/test" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Using localtunnel

```bash
npm install -g localtunnel
lt --port 3000
```

Use the provided URL as your webhook URL.

> **Warning**
>
> Free ngrok accounts have session limits. The tunnel will disconnect and you'll need to restart ngrok and update your webhook URL. For persistent tunnels, consider a paid ngrok plan or deploying to a cloud service.

## Test webhook endpoint

Use the test endpoint to verify your webhook handler is working correctly. This sends a sample `agent.message` payload to your configured URL:

```bash
curl -X POST "https://api.agentphone.ai/v1/webhooks/test" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json
{
  "success": true,
  "httpStatus": 200,
  "errorMessage": null
}
```

You can also test per-agent webhooks:

```bash
curl -X POST "https://api.agentphone.ai/v1/agents/agt_abc123/webhook/test" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

## Mock webhook payloads

Use these sample payloads to test your webhook handler locally with `curl` or in your test suite.

### SMS payload

```json
{
  "event": "agent.message",
  "channel": "sms",
  "timestamp": "2025-12-03T10:05:00Z",
  "agentId": "agent_123",
  "data": {
    "conversationId": "conv_test456",
    "numberId": "num_abc",
    "from": "+14155551234",
    "to": "+18571234567",
    "message": "Test message",
    "direction": "inbound",
    "receivedAt": "2025-12-03T10:05:00Z"
  },
  "conversationState": { "testMode": true },
  "recentHistory": [
    { "content": "Hello", "direction": "inbound", "channel": "sms", "at": "2025-12-03T10:04:00Z" }
  ]
}
```

### Voice payload

```json
{
  "event": "agent.message",
  "channel": "voice",
  "timestamp": "2025-12-03T10:05:00Z",
  "agentId": "agent_123",
  "data": {
    "callId": "call_abc123",
    "numberId": "num_abc",
    "from": "+14155551234",
    "to": "+18571234567",
    "status": "in-progress",
    "transcript": "I need help with my order",
    "confidence": 0.95,
    "direction": "inbound"
  },
  "conversationState": null,
  "recentHistory": [
    { "content": "Hello, how can I help?", "direction": "outbound", "channel": "voice", "at": "2025-12-03T10:04:30Z" }
  ]
}
```

### Testing with curl

Send a mock payload to your local server:

```bash
curl -X POST http://localhost:3000/webhook \
  -H "Content-Type: application/json" \
  -d '{
    "event": "agent.message",
    "channel": "voice",
    "agentId": "agent_123",
    "data": {
      "callId": "call_test",
      "from": "+14155551234",
      "to": "+18571234567",
      "transcript": "What are your hours?",
      "confidence": 0.95,
      "direction": "inbound"
    },
    "conversationState": null,
    "recentHistory": []
  }'
```

## Example test handlers

### Python (Flask)

```python
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.json
    channel = payload.get('channel')
    data = payload.get('data', {})

    if channel == 'sms':
        print(f"SMS from {data['from']}: {data['message']}")
        return jsonify({'status': 'ok'}), 200

    if channel == 'voice':
        print(f"Voice from {data['from']}: {data['transcript']}")
        return jsonify({'text': f"I heard: {data['transcript']}"}), 200

    return jsonify({'status': 'ok'}), 200

if __name__ == '__main__':
    app.run(port=3000)
```

### Node.js (Express)

```javascript
const express = require('express');
const app = express();
app.use(express.json());

app.post('/webhook', (req, res) => {
  const { channel, data } = req.body;

  if (channel === 'sms') {
    console.log(`SMS from ${data.from}: ${data.message}`);
    return res.status(200).json({ status: 'ok' });
  }

  if (channel === 'voice') {
    console.log(`Voice from ${data.from}: ${data.transcript}`);
    return res.status(200).json({ text: `I heard: ${data.transcript}` });
  }

  res.status(200).json({ status: 'ok' });
});

app.listen(3000, () => console.log('Webhook server on port 3000'));
```

## Checking webhook deliveries

Monitor delivery status to debug issues:

```bash
curl "https://api.agentphone.ai/v1/webhooks/deliveries?limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

The response shows delivery attempts, HTTP status codes, error messages, and retry timestamps. Use this to verify your endpoint is receiving and acknowledging webhooks correctly.