# Fulfill purchases through the backend API

> Implement your own backend that listens to purchase events and tracks your player's entitlements.

If your game uses server-authoritative entitlements, implement a backend system that does the following:

1. [Validate the webhook event (JWT)](#validate-webhook-events-\(jwt\)).
2. [Parse the event](#webhook-event-shape).
3. Update entitlements or inventory in your system based on the [event type](#event-types).
4. [Mark the order as fulfilled](#mark-orders-as-fulfilled-via-api).
5. [Return an event response](#return-an-event-response).

This ensures both your system and Unity IAP are in sync.

> **Important:**
>
> You must mark every paid order as fulfilled, even though your backend grants the player's entitlements. The player keeps their entitlement either way, but an order that stays in the `paid` state leaves Unity IAP order tracking and analytics incomplete. In hybrid setups that mix the SDK and webhooks, unacknowledged orders can also cause order state to drift between systems.

## Validate webhook events (JWT)

To enable validation, Unity IAP signs webhook requests with a JSON Web Token (JWT). Verify the following information for this token in your backend to ensure events are authentic:

* **Authorization header**: `Bearer <token>`
* **Signature**: Signed with a private key. Fetch and cache the **JWKS (public key)** to verify: [`https://services.api.unity.com/webhooks/.well-known/jwks.json`](https://services.api.unity.com/webhooks/.well-known/jwks.json)
* **Issuer**: `https://services.api.unity.com/webhooks/`
* **Audience claims (array)**: `upid` (Unity Project Id), `envId` (Environment Id)
* **Token expiration**: Ensure `exp` (expiration) has not passed.

There are [libraries for most languages](https://www.jwt.io/libraries?programming_language) that you can use to validate JWTs.

## Webhook event shape

Unity IAP normalizes events from all supported payment providers into this consistent format. Refer to the following example:

```json
{
  "id": "018d5e5e-5e5e-7e5e-5e5e-5e5e5e5e5e5e",
  "version": "1.0.0",
  "eventType": "order.paid",
  "time": "2024-01-15T14:30:00Z",
  "projectId": "018d5e5e-1111-7e5e-5e5e-111111111111",
  "environmentId": "018d5e5e-2222-7e5e-5e5e-222222222222",
  "dataType": "order",
  "data": {
    "id": "018d5e5e-3333-7e5e-5e5e-333333333333",
    "playerId": "player_12345",
    "paymentProvider": "stripe",
    "paymentProviderResourceId": "cs_test_a1b2c3d4e5f6",
    "url": "https://checkout.stripe.com/pay/cs_test_a1b2c3d4e5f6",
    "lineItems": [
      {
        "sku": "com.game.coins_100",
        "productType": "Consumable",
        "price": {
          "amountMicros": 4990000,
          "currency": "USD"
        }
      }
    ],
    "total": {
      "amountMicros": 4990000,
      "currency": "USD",
      "refundedAmountMicros": 0
    },
    "status": "paid",
    "customReferenceId": "order_xyz_789",
    "metadata": {
      "campaign": "summer_sale",
      "platform": "iOS"
    },
    "createdAt": "2024-01-15T14:25:00Z",
    "updatedAt": "2024-01-15T14:30:00Z",
    "paidAt": "2024-01-15T14:30:00Z",
    "fulfilledAt": null
  }
}
```

Refer to the following fields in webhook events:

**id** (string, required): A unique identifier for the webhook event.**version** (string, required): The version of the webhook event schema.**eventType** (string, required): The type of the event. Refer to [Event types](#event-types) for possible values.**time** (string, required): The time the event was generated (ISO 8601 format).**projectId** (string, required): The ID of the Unity project this order belongs to.**environmentId** (string, required): The ID of the Unity environment this order occurred in.**dataType** (string, required): The type of data in the event.**data** (object, required): The order data object containing details about the purchase.**id** (string, required): A unique identifier for the order.**playerId** (string, required): The Unity authentication player ID.**paymentProvider** (string, required): The payment provider that processed the order, for example, `stripe` or `coda`.**paymentProviderResourceId** (string, required): The unique ID assigned to the resource by the payment provider.**url** (string, required): The checkout URL for the order.**lineItems** (object array, required): A list of products purchased in the order.**sku** (string, required): The unique identifier for the product.**productType** (string, required): The type of product, for example, `Consumable`.**price** (object, required): The price of the product.**amountMicros** (integer, required): The price in micros (1,000,000 micros equals $1.00). These values are what was shown to the user.**currency** (string, required): The three-letter ISO 4217 currency code.**total** (object, required): The total order amount.**amountMicros** (integer, required): The total order amount in micros.**currency** (string, required): The three-letter ISO 4217 currency code.**refundedAmountMicros** (integer, required): The total refunded amount in micros. In partial refunds, this may be smaller than `amountMicros`.**status** (string, required): The status of the order. Possible values: `created`, `paid`, `failed`, `fulfilled`, `revoked`.**customReferenceId** (string): An optional custom reference ID for the order.**metadata** (object): A key-value object containing any custom metadata passed during the purchase flow.**createdAt** (string, required): The timestamp when the order was created (ISO 8601 format).**updatedAt** (string, required): The timestamp when the order was last updated (ISO 8601 format).**paidAt** (string): The timestamp when the order was paid (ISO 8601 format).**fulfilledAt** (string): The timestamp when the order was fulfilled, or `null` if not yet fulfilled.**revokedAt** (string): The timestamp when the order was revoked, or `null` if not revoked.

## Event types

Unity IAP sends the following webhook event types:

### order.paid

Unity IAP sends the `order.paid` event when a player successfully completes a purchase. When you receive this event, grant the player the entitlements or currency they purchased and [mark the order as fulfilled](#mark-orders-as-fulfilled-via-api).

### order.updated

Unity IAP sends the `order.updated` event every time the order is updated. This includes changes such as the following:

* The order being marked as fulfilled.
* A refund being processed.
* Other order modifications.

The `refundedAmountMicros` field in `data.total` reflects the current total refunded amount.

Refunds are developer-initiated. For example, you might issue a refund to a player through the payment provider's dashboard.

Unity IAP doesn't automatically revoke entitlements when a refund occurs. If you want to revoke entitlements on refund, you can listen for this event and handle it in your own entitlement system.

For the wider refund process and who owns it, refer to [Refunds and disputes for D2C payments](./refunds-and-disputes.md#refunds).

### order.revoked

Unity IAP sends the `order.revoked` event when the order status changes to `revoked`. When you receive this event, revoke the player's entitlements or currency that was granted for the original purchase.

Chargebacks are player-initiated. For example, a player might dispute a charge through their credit card company or bank.

When a chargeback dispute closes and the player wins the dispute, Unity IAP automatically revokes the order and sends this event. Unlike refunds, Unity IAP revokes entitlements on chargebacks.

For who resolves a dispute and what your payment provider covers, refer to [Refunds and disputes for D2C payments](./refunds-and-disputes.md#disputes-and-chargebacks).

## Mark orders as fulfilled via API

After granting entitlements on your backend, use the Orders API to mark the order as fulfilled. This updates the `fulfilledAt` timestamp and ensures the order status is tracked correctly.

### Authentication

To call these endpoints, you need to authenticate using one of the following methods:

* **Service account**: Create a service account in the Unity Dashboard and use it to authenticate your backend server. For more information, refer to [Service account authentication](https://services.docs.unity.com/docs/service-account-auth/).
* **Cloud Code**: Call the endpoint from a Cloud Code script or module, which can authenticate on behalf of your project. For more information, refer to [Use a Cloud Code module to fulfill purchases](./cloud-code-fulfillment.md).

### Get order

Call the [`GET` endpoint](/oas-iap-client/1.0.0/.md#get-order-status) to retrieve the order.

```http
GET https://iap.services.api.unity.com/v1/projects/{projectId}/environments/{environmentId}/orders/{orderId}
```

#### Order statuses

| **Status**  | **Description**                                                                                                |
| ----------- | -------------------------------------------------------------------------------------------------------------- |
| `created`   | Order has been created but payment has not been completed. Can transition to `paid`, `failed`, or `cancelled`. |
| `paid`      | Payment has been received and confirmed by the payment provider. Can transition to `fulfilled` or `revoked`.   |
| `fulfilled` | Order has been fulfilled and the player has been rewarded. Can transition to `revoked`.                        |
| `failed`    | Order failed. This is a terminal state.                                                                        |
| `revoked`   | Order has been revoked. This is a terminal state.                                                              |
| `cancelled` | Order has been cancelled before payment. This is a terminal state.                                             |

#### Example response body

Both `GET` and `PATCH` endpoints return the full order object:

```json
{
  "id": "018d5e5e-3333-7e5e-5e5e-333333333333",
  "projectId": "018d5e5e-1111-7e5e-5e5e-111111111111",
  "environmentId": "018d5e5e-2222-7e5e-5e5e-222222222222",
  "playerId": "player_12345",
  "paymentProvider": "stripe",
  "paymentProviderResourceId": "cs_test_12345",
  "url": "https://checkout.stripe.com/pay/cs_test_12345",
  "lineItems": [
    {
      "sku": "com.game.coins_100",
      "productType": "Consumable"
    }
  ],
  "status": "paid",
  "fulfilledAt": null,
  "revokedAt": null,
  "customReferenceId": "order_xyz_789",
  "metadata": {
    "campaign": "summer_sale"
  },
  "createdAt": "2024-01-15T14:25:00Z",
  "updatedAt": "2024-01-15T14:30:00Z"
}
```

### Update an order

Call the [`PATCH` endpoint](/oas-iap-client/1.0.0/.md#update-an-order) to update an order's status:

```http
PATCH https://iap.services.api.unity.com/v1/projects/{projectId}/environments/{environmentId}/orders/{orderId}
```

#### Request body

```json
{
  "status": "fulfilled"
}
```

**status** (string, required): The new status to set for the order. Allowed values:- `fulfilled`: Set after granting entitlements to the player. Can only be set on `paid` orders.
- `revoked`: Set to revoke the player's entitlements (for example, after a refund). Can only be set on `paid` or `fulfilled` orders.
- `cancelled`: Set to cancel the order. Can only be set on `created` orders (before payment).Once an order is `revoked` or `cancelled`, its status cannot be changed.

You can refer to the IAP Client API documentation for the [HTTP response status codes](/oas-iap-client/1.0.0/.md#http-response-status-codes-for-update-an-order:).

## Return an event response

You need to return an event response to indicate the success or failure of processing the event.

* **`2xx` response**: Indicates successful event processing.
* **Non-`2xx` response**: Indicates a failure. Unity IAP will [replay the event](#event-replay-behavior) according to the replay policy.

### Event replay behavior

When your webhook endpoint returns a non-`2xx` response, Unity IAP automatically retries delivery of the event. Ensure your webhook handler is idempotent so that replayed events don't result in duplicate entitlements.

## Next steps

This page is part of a workflow to set up Direct-to-Consumer (D2C) payment providers with IAP. To continue this workflow, choose one of the following options:

[Integrate D2C payment providers](./workflow.md#fulfill-purchases-through-the-backend-api): Return to the Integrate D2C payment providers with IAP workflow page.
[Test your integration](./test-integration.md): Proceed to the next step in the workflow to set up your D2C payment provider.
