# Zalo Official Account Integration Guide

Complete guide for integrating with Zalo Official Account API.

## Prerequisites

### 1. Zalo Official Account

Register and verify your business account at https://oa.zalo.me/

**Requirements**:
- Business license
- Vietnamese business registration
- 3-5 business days for verification

### 2. Zalo Application

Create application at https://developers.zalo.me/

1. Create new application
2. Link to your Official Account
3. Note `App ID` and `Secret Key`

### 3. Access Token

Get OA Access Token (1-year validity):

**Via Portal** (Development):
1. Go to https://developers.zalo.me/tools/explorer
2. Select your application and OA
3. Click "Get Access Token"
4. Copy the token

**Via API** (Production):
```http
POST https://oauth.zaloapp.com/v4/oa/access_token
Content-Type: application/x-www-form-urlencoded

app_id={app_id}&secret_key={secret_key}&code={authorization_code}
```

---

## Webhook Configuration

### 1. Set Webhook URL

In Zalo Developers portal:
1. Navigate to "Webhooks" tab
2. Set URL: `https://yourdomain.com/api/v1/zalo/webhooks`
3. Set secret key for signature verification

### 2. Enable Events

Required events:
- ✅ `user_send_text` - Text messages
- ✅ `user_send_image` - Image messages
- ✅ `follow` - New followers
- ✅ `unfollow` - Unfollows

### 3. Signature Verification

```csharp
public class ZaloWebhookVerifier
{
    private readonly string _secretKey;
    
    public bool VerifySignature(string body, string signature)
    {
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_secretKey));
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(body));
        var computed = BitConverter.ToString(hash).Replace("-", "").ToLower();
        
        return signature.Equals(computed, StringComparison.OrdinalIgnoreCase);
    }
}
```

**Headers**:
```
X-Zalo-Signature: <hmac_sha256_signature>
```

### 4. Response Requirements

**CRITICAL**: Must return `200 OK` within 2 seconds

```csharp
[HttpPost("webhooks")]
public async Task<IActionResult> ReceiveWebhook([FromBody] ZaloWebhookEvent webhook)
{
    // 1. Verify signature (FAST)
    if (!_verifier.VerifySignature(RequestBody, Request.Headers["X-Zalo-Signature"]))
        return Unauthorized();
    
    // 2. Queue for async processing (FAST)
    await _messageQueue.PublishAsync(webhook);
    
    // 3. Return immediately (< 2s)
    return Ok(new { success = true });
}
```

---

## Zalo API Endpoints

### Base URL
```
https://openapi.zalo.me
```

### 1. Send Text Message

```http
POST /v3.0/oa/message/cs
Authorization: access_token {your_access_token}
Content-Type: application/json

{
  "recipient": {
    "user_id": "1234567890123456789"
  },
  "message": {
    "text": "Xin chào! Tôi có thể giúp gì cho bạn?"
  }
}
```

**Response**:
```json
{
  "error": 0,
  "message": "Success",
  "data": {
    "message_id": "zalo_msg_xyz123"
  }
}
```

### 2. Get User Profile

```http
GET /v3.0/oa/user/detail?data={"user_id":"1234567890123456789"}
Authorization: access_token {your_access_token}
```

**Response**:
```json
{
  "error": 0,
  "data": {
    "user_id": "1234567890123456789",
    "display_name": "Nguyễn Văn A",
    "avatar": "https://s.zalo.me/avatar.jpg",
    "user_gender": 1
  }
}
```

### 3. Send ZNS Template

```http
POST /v3.0/oa/message/template
Authorization: access_token {your_access_token}

{
  "phone": "+84901234567",
  "template_id": "283746",
  "template_data": {
    "order_id": "ORD-12345",
    "total_amount": "500000"
  }
}
```

---

## Event Types

### user_send_text

```json
{
  "event_name": "user_send_text",
  "sender": { "id": "1234567890123456789" },
  "message": {
    "text": "Xin chào",
    "msg_id": "msg_123456"
  }
}
```

### user_send_image

```json
{
  "event_name": "user_send_image",
  "message": {
    "attachments": [{
      "type": "image",
      "payload": {
        "url": "https://zalo-api.zadn.vn/...",
        "thumbnail": "https://..."
      }
    }]
  }
}
```

### follow

```json
{
  "event_name": "follow",
  "follower": { "id": "1234567890123456789" }
}
```

---

## Rate Limits

| API | Limit |
|-----|-------|
| Send Message (CS) | 1000 requests/day per user |
| Send ZNS | 10000 requests/day |
| Get User Profile | 5000 requests/day |

---

## Error Handling

### Common Error Codes

| Code | Message | Resolution |
|------|---------|------------|
| -201 | Invalid access token | Refresh token |
| -213 | User blocked OA | Mark customer inactive |
| -214 | User inactive 7+ days | Use ZNS instead |
| -300 | Rate limit exceeded | Wait and retry |

### Retry Strategy

```csharp
var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(r => (int)r.StatusCode >= 500)
    .WaitAndRetryAsync(3, retryAttempt => 
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
```

---

## Best Practices

### Webhook Processing

✅ **DO**:
- Verify signature on every webhook
- Return 200 within 2 seconds
- Process asynchronously via queue
- Store raw events for 7 days

❌ **DON'T**:
- Make database queries in handler
- Call external APIs synchronously
- Process LLM in webhook handler

### Message Quality

✅ **DO**:
- Keep messages under 2000 characters
- Use ZNS for important notifications
- Respect OA block status

❌ **DON'T**:
- Send spam
- Send marketing without consent

---

## Testing

### Manual Testing

```bash
# Send test webhook
curl -X POST http://localhost:8080/api/v1/zalo/webhooks \
  -H "Content-Type: application/json" \
  -H "X-Zalo-Signature: <signature>" \
  -d '{
    "event_name": "user_send_text",
    "sender": {"id": "test_user"},
    "message": {"text": "hello"}
  }'
```

---

**Document Version**: 1.0  
**Last Updated**: 2026-01-18
