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

# Trao Quyền

> Tải trọng được gửi đến điểm cuối webhook của bạn khi một quyền được tạo, giao hàng, thất bại hoặc bị thu hồi.

## Sự Kiện Webhook Trao Quyền

Các sự kiện này được kích hoạt mỗi khi quyền của khách hàng thay đổi trạng thái, ví dụ khi một license key được tạo, một vai trò Discord được gán, một liên kết tải xuống được cung cấp hoặc quyền truy cập bị từ chối. Đăng ký các sự kiện này để giữ cho ứng dụng của bạn đồng bộ với những gì mỗi khách hàng có thể truy cập.

| Sự Kiện                       | Mô Tả                                                                                                                                   |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `entitlement_grant.created`   | Một dòng cấp quyền mới đã được tạo. Trạng thái là `delivered` ngay lập tức đối với các license key, và `pending` cho mọi tích hợp khác. |
| `entitlement_grant.delivered` | Quyền chuyển sang được giao hàng. Khách hàng hiện có quyền truy cập vào nền tảng, tập tin hoặc license key đã được cấp.                 |
| `entitlement_grant.failed`    | Giao hàng thất bại và không được thử lại. Kiểm tra `error_code` và `error_message`.                                                     |
| `entitlement_grant.revoked`   | Quyền truy cập đã bị rút lại. Kiểm tra `revocation_reason` để hiểu lý do.                                                               |

Cả bốn sự kiện đều chia sẻ cùng một tải trọng `EntitlementGrantResponse` được tài liệu hóa trong sơ đồ dưới đây.

***

## Kích Hoạt Sự Kiện

### entitlement\_grant.created

Một dòng cấp quyền vừa được chèn. Quyền luôn có một `id` ổn định từ lúc này, ngay cả khi trạng thái của nó thay đổi. Sử dụng sự kiện này để ghi lại rằng việc thực hiện đang diễn ra.

Đối với **license keys** dòng được chèn trực tiếp với `status: "delivered"` và `delivered_at` đã được điền, vì vậy một sự kiện `created` duy nhất được theo sau mà không có sự thay đổi trạng thái nào nữa trừ khi quyền sau đó bị thu hồi.

Đối với **mọi tích hợp khác** dòng đến với `status: "pending"`. Một sự kiện `delivered` hoặc `failed` theo sau khi hoàn thành giao hàng:

* **Tích hợp dựa trên OAuth** (Discord, GitHub, Notion) bao gồm một `oauth_url` mà khách hàng phải truy cập để hoàn tất ủy quyền. Quyền vẫn ở `pending` cho đến khi khách hàng ủy quyền.
* **Tích hợp trực tiếp qua nền tảng** (Telegram, Framer, Digital Files) chỉ ở `pending` trong một thời gian ngắn trong khi cuộc gọi nền tảng diễn ra, sau đó chuyển đến `delivered`.

### entitlement\_grant.delivered

Quyền đã chuyển từ `pending` sang `delivered`. Khách hàng hiện có quyền truy cập như đã mô tả trong quyền. Sử dụng sự kiện này để mở khóa các tính năng phụ thuộc trong hệ thống của bạn, ví dụ để cung cấp một không gian làm việc, gửi email chào mừng tùy chỉnh hoặc đánh dấu một cờ "đã hoàn thành".

Trường `delivered_at` của tải trọng ghi lại khi quá trình giao hàng hoàn tất. Đối với những quyền đã nhận `delivered` khi tạo, bạn sẽ nhận sự kiện `created` và `delivered` liên tiếp.

### entitlement\_grant.failed

Giao hàng đã được thử nghiệm nhưng thất bại với lỗi không thể thử lại. Các trường `error_code` và `error_message` giải thích lý do thất bại. Nguyên nhân phổ biến bao gồm token OAuth bị thu hồi, quyền nền tảng bị từ chối, hoặc mục tiêu bị thiếu (ví dụ, một guild Discord đã bị xóa).

<Tip>
  Xử lý `entitlement_grant.failed` như một hành động có thể thực hiện. Khách hàng đã thanh toán nhưng không nhận được quyền truy cập. Hiển thị các thất bại cho đội ngũ hỗ trợ của bạn hoặc kích hoạt lại sau khi vấn đề cơ bản được giải quyết.
</Tip>

### entitlement\_grant.revoked

Quyền truy cập đã bị rút lại ở cấp độ nền tảng: vai trò Discord bị xóa, cộng tác viên GitHub bị xóa, key giấy phép bị vô hiệu hóa, URL tải xuống tệp không còn được cấp phát. Trường `revocation_reason` ghi lại kích hoạt.

| `revocation_reason`      | Kích Hoạt                                                                                                                                                                                                                                                                               |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `subscription_cancelled` | Đăng ký của khách hàng đã bị hủy (sự kiện `subscription.cancelled`).                                                                                                                                                                                                                    |
| `subscription_on_hold`   | Đăng ký đang bị tạm giữ do gia hạn thất bại (`subscription.on_hold`). Có thể khôi phục: một lần thử lại thành công sẽ cấp quyền lại.                                                                                                                                                    |
| `subscription_expired`   | Đăng ký đã đạt đến ngày kết thúc nhiệm kỳ (`subscription.expired`).                                                                                                                                                                                                                     |
| `plan_changed`           | Kế hoạch đã thay đổi; quyền cũ bị thu hồi trước khi cấp quyền mới (`subscription.plan_changed`).                                                                                                                                                                                        |
| `refund`                 | Đã xử lý một khoản hoàn tiền cho thanh toán một lần ban đầu (`refund.succeeded`).                                                                                                                                                                                                       |
| `manual`                 | Một nhà cung cấp hủy bỏ quyền thông qua API hoặc bảng điều khiển. Khi thu hồi thủ công, sẽ không tự động cấp lại khi gia hạn đăng ký.                                                                                                                                                   |
| `license_key_disabled`   | Key sau một quyền cấp license-key đã bị vô hiệu hóa. Quyền được kích hoạt lại tự động nếu key được tái kích hoạt.                                                                                                                                                                       |
| `platform_external`      | Phía nền tảng của một tích hợp không đồng bộ (ví dụ, vai trò Discord bị xóa thủ công, Ứng dụng GitHub mất quyền truy cập kho, hoặc kiểm tra đối chiếu phát hiện thiếu mục tiêu). Quyền sẽ không tự động cấp lại khi gia hạn đăng ký cho đến khi vấn đề nền tảng cơ bản được giải quyết. |

***

## Biến Thể Payload

Trường `data` luôn là một đối tượng `EntitlementGrantResponse`. Hai kiểu tích hợp đính kèm thêm các đối tượng con:

* **`license_key`** được bao gồm khi kiểu tích hợp quyền là `license_key`. Nó chứa key đã tạo, ngày hết hạn và sử dụng kích hoạt.
* **`digital_product_delivery`** được bao gồm khi kiểu tích hợp là `digital_files`. Nó chứa các URL tải xuống đã ký trước, tùy chọn `instructions` và tùy chọn `external_url`.

Đối với tất cả các kiểu tích hợp khác (Discord, GitHub, Telegram, Framer, Notion) cả hai trường là `null`; cấu hình liên quan được ghi lại trong chính quyền, không phải trong cấp quyền.

***

## Payload Ví Dụ

### License key được giao (`entitlement_grant.delivered`)

```json theme={null} theme={null}
{
  "business_id": "bus_H4ekzPSlcg",
  "type": "entitlement_grant.delivered",
  "timestamp": "2026-05-01T10:25:33.000000Z",
  "data": {
    "id": "grant_8VbC6JDZzPEqfBPUdpj0K",
    "business_id": "bus_H4ekzPSlcg",
    "entitlement_id": "ent_9xY2bKwQn5MjRpL8d",
    "customer_id": "cus_abc123",
    "external_id": "lk_AAA111BBB222",
    "payment_id": "pay_a1b2c3d4",
    "subscription_id": null,
    "status": "delivered",
    "license_key": {
      "key": "PRO-AAAA-BBBB-CCCC-DDDD",
      "expires_at": "2027-05-01T00:00:00Z",
      "activations_used": 0,
      "activations_limit": 5
    },
    "digital_product_delivery": null,
    "delivered_at": "2026-05-01T10:25:33Z",
    "revoked_at": null,
    "revocation_reason": null,
    "error_code": null,
    "error_message": null,
    "oauth_url": null,
    "oauth_expires_at": null,
    "metadata": null,
    "created_at": "2026-05-01T10:25:33Z",
    "updated_at": "2026-05-01T10:25:33Z"
  }
}
```

### Tệp kỹ thuật số được giao (`entitlement_grant.delivered`)

```json theme={null} theme={null}
{
  "business_id": "bus_H4ekzPSlcg",
  "type": "entitlement_grant.delivered",
  "timestamp": "2026-05-01T10:30:12.000000Z",
  "data": {
    "id": "grant_2P9rQwYvMxTnKoCb4",
    "business_id": "bus_H4ekzPSlcg",
    "entitlement_id": "ent_files_J3kLmN4oP5",
    "customer_id": "cus_abc123",
    "external_id": "pay_a1b2c3d4",
    "payment_id": "pay_a1b2c3d4",
    "subscription_id": null,
    "status": "delivered",
    "license_key": null,
    "digital_product_delivery": {
      "files": [
        {
          "file_id": "df_a4f6c1de",
          "download_url": "https://files.dodopayments.com/.../pro-bundle.zip?Signature=...",
          "filename": "pro-bundle.zip",
          "content_type": "application/zip",
          "file_size": 18742390,
          "expires_in": 900
        }
      ],
      "instructions": "Unzip and run setup.sh from the project root.",
      "external_url": null
    },
    "delivered_at": "2026-05-01T10:30:12Z",
    "revoked_at": null,
    "revocation_reason": null,
    "error_code": null,
    "error_message": null,
    "oauth_url": null,
    "oauth_expires_at": null,
    "metadata": null,
    "created_at": "2026-05-01T10:30:12Z",
    "updated_at": "2026-05-01T10:30:12Z"
  }
}
```

### Vai trò Discord được tạo và đang chờ xử lý (`entitlement_grant.created`)

```json theme={null} theme={null}
{
  "business_id": "bus_H4ekzPSlcg",
  "type": "entitlement_grant.created",
  "timestamp": "2026-05-01T10:31:00.000000Z",
  "data": {
    "id": "grant_DiscordPending5L",
    "business_id": "bus_H4ekzPSlcg",
    "entitlement_id": "ent_discord_patrons",
    "customer_id": "cus_abc123",
    "external_id": "sub_pro_monthly_001",
    "payment_id": null,
    "subscription_id": "sub_pro_monthly_001",
    "status": "pending",
    "license_key": null,
    "digital_product_delivery": null,
    "delivered_at": null,
    "revoked_at": null,
    "revocation_reason": null,
    "error_code": null,
    "error_message": null,
    "oauth_url": "https://discord.com/oauth2/authorize?...",
    "oauth_expires_at": "2026-05-08T10:31:00Z",
    "metadata": null,
    "created_at": "2026-05-01T10:31:00Z",
    "updated_at": "2026-05-01T10:31:00Z"
  }
}
```

### Quyền bị thu hồi khi hủy đăng ký (`entitlement_grant.revoked`)

```json theme={null} theme={null}
{
  "business_id": "bus_H4ekzPSlcg",
  "type": "entitlement_grant.revoked",
  "timestamp": "2026-06-15T08:12:44.000000Z",
  "data": {
    "id": "grant_8VbC6JDZzPEqfBPUdpj0K",
    "business_id": "bus_H4ekzPSlcg",
    "entitlement_id": "ent_9xY2bKwQn5MjRpL8d",
    "customer_id": "cus_abc123",
    "external_id": "sub_pro_monthly_001",
    "payment_id": null,
    "subscription_id": "sub_pro_monthly_001",
    "status": "revoked",
    "revocation_reason": "subscription_cancelled",
    "license_key": {
      "key": "PRO-AAAA-BBBB-CCCC-DDDD",
      "expires_at": null,
      "activations_used": 1,
      "activations_limit": 5
    },
    "digital_product_delivery": null,
    "delivered_at": "2026-05-01T10:25:33Z",
    "revoked_at": "2026-06-15T08:12:44Z",
    "error_code": null,
    "error_message": null,
    "oauth_url": null,
    "oauth_expires_at": null,
    "metadata": null,
    "created_at": "2026-05-01T10:25:33Z",
    "updated_at": "2026-06-15T08:12:44Z"
  }
}
```

### Giao hàng thất bại (`entitlement_grant.failed`)

```json theme={null} theme={null}
{
  "business_id": "bus_H4ekzPSlcg",
  "type": "entitlement_grant.failed",
  "timestamp": "2026-05-01T10:36:21.000000Z",
  "data": {
    "id": "grant_GhFailed7Z",
    "business_id": "bus_H4ekzPSlcg",
    "entitlement_id": "ent_github_repo",
    "customer_id": "cus_abc123",
    "external_id": "pay_a1b2c3d4",
    "payment_id": "pay_a1b2c3d4",
    "subscription_id": null,
    "status": "failed",
    "license_key": null,
    "digital_product_delivery": null,
    "delivered_at": null,
    "revoked_at": null,
    "revocation_reason": null,
    "error_code": "github_permission_denied",
    "error_message": "Repository access could not be granted: the GitHub App installation no longer has permission on this repository.",
    "oauth_url": null,
    "oauth_expires_at": null,
    "metadata": null,
    "created_at": "2026-05-01T10:36:00Z",
    "updated_at": "2026-05-01T10:36:21Z"
  }
}
```

***

## Mẹo Tích Hợp

* **Chờ `entitlement_grant.delivered` trước khi mở khóa các tính năng phụ thuộc.** Một sự kiện `payment.succeeded` cho bạn biết tiền đã được giải ngân; nó không cho bạn biết rằng khách hàng đã có repo GitHub hoặc vai trò Discord. Sự kiện `delivered` là nguồn sự thật cho việc thực hiện.
* **Lập bản đồ `revocation_reason` đến luồng giữ chân người dùng.** Một quyền hồi lại `subscription_on_hold` thường có nghĩa là thẻ của khách hàng đã thất bại và lần gia hạn tiếp theo sẽ cấp lại quyền. Một quyền hồi lại `manual` hoặc `subscription_cancelled` là có chủ ý. Xử lý chúng khác nhau trong thông báo khách hàng.
* **Sử dụng quyền `id` như là khóa idempotency của bạn.** Một quyền duy nhất phát ra nhiều nhất một sự kiện `created` và nhiều nhất một sự kiện cuối cùng (`delivered` hoặc `failed`), và nhiều nhất một sự kiện `revoked`. Các đợt giao lại từ hệ thống webhook có thể lặp lại các sự kiện; loại bỏ trùng lặp trên cấp quyền `id` cộng `type`.
* **Kiểm tra `license_key` và `digital_product_delivery` để nhận biết kiểu tích hợp.** Bản thân tải trọng quyền không mang kiểu tích hợp, nhưng chính xác một trong các đối tượng con này được điền cho các quyền license-key và tệp kỹ thuật số.
* **Đối với các quyền dựa trên OAuth, hiển thị `oauth_url` cho khách hàng.** Sự kiện `entitlement_grant.created` cho luồng thuê bao Discord, GitHub hoặc Notion bao gồm một `oauth_url` và `oauth_expires_at`. Gửi email nó cho khách hàng hoặc hiển thị trong ứng dụng của bạn để giải phóng việc giao hàng.

```json theme={null} theme={null}
{
  "business_id": "bus_H4ekzPSlcg",
  "type": "entitlement_grant.failed",
  "timestamp": "2026-05-01T10:36:21.000000Z",
  "data": {
    "id": "grant_GhFailed7Z",
    "business_id": "bus_H4ekzPSlcg",
    "entitlement_id": "ent_github_repo",
    "customer_id": "cus_abc123",
    "external_id": "pay_a1b2c3d4",
    "payment_id": "pay_a1b2c3d4",
    "subscription_id": null,
    "status": "failed",
    "integration_type": "github",
    "license_key": null,
    "digital_product_delivery": null,
    "delivered_at": null,
    "revoked_at": null,
    "revocation_reason": null,
    "error_code": "github_permission_denied",
    "error_message": "Repository access could not be granted: the GitHub App installation no longer has permission on this repository.",
    "oauth_url": null,
    "oauth_expires_at": null,
    "metadata": null,
    "created_at": "2026-05-01T10:36:00Z",
    "updated_at": "2026-05-01T10:36:21Z"
  }
}
```

***

## Mẹo Tích Hợp

* **Đợi đến khi `entitlement_grant.delivered` trước khi mở khóa các tính năng phụ thuộc.** Một sự kiện `payment.succeeded` thông báo rằng tiền đã được làm rõ; sự kiện này không cho biết khách hàng đã có GitHub repo hay vai trò Discord chưa. Sự kiện `delivered` là nguồn xác thực cho việc thực hiện.
* **Map `revocation_reason` đến các luồng duy trì.** Một sự thu hồi `subscription_on_hold` thường có nghĩa là thẻ của khách hàng bị lỗi và việc gia hạn tiếp theo sẽ cấp lại quyền truy cập. Thu hồi `manual` hoặc `subscription_cancelled` là có chủ ý. Xử lý chúng khác nhau trong việc nhắn tin cho khách hàng.
* **Sử dụng cấp phát `id` làm khóa idempotency của bạn.** Một lần cấp phát chỉ phát ra tối đa một sự kiện `created` và tối đa một sự kiện cuối cùng (`delivered` hoặc `failed`), và tối đa một sự kiện `revoked`. Đợt chuyển giao lại từ hệ thống webhook có thể lặp lại các sự kiện; loại bỏ các sự kiện trùng lặp trên cấp phát `id` cộng với `type`.
* **Đọc `integration_type` để nhận biết loại cấp phát.** Payload chứa `integration_type` trực tiếp (ví dụ `license_key`, `digital_files`, `discord`). Các đối tượng lồng nhau `license_key` và `digital_product_delivery` được điền khi các quyền cấp phát tương ứng của chúng được giao; một quyền cấp phát khóa giấy phép được thực hiện thủ công sẽ duy trì `pending` với `integration_type: "license_key"` và một `null` `license_key` cho đến khi bạn thực hiện nó.
* **Đối với các quyền cấp phát dựa trên OAuth, hiển thị `oauth_url` cho khách hàng.** Sự kiện `entitlement_grant.created` cho các luồng đăng ký Discord, GitHub hoặc Notion bao gồm `oauth_url` và `oauth_expires_at`. Gửi email cho khách hàng hoặc hiển thị trong ứng dụng của bạn để không cản trở việc giao hàng.
