# 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.

### 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.

## 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.
