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

# Mode C: direct fulfillment API

> Signed URL redirect, direct fulfillment, fulfillment inquiry, and the fulfillment result webhook. After the user completes payment, Waffo Point Topup calls the supplier's endpoint to credit the user's account directly.

Mode C is for suppliers that can credit user accounts directly. After the user completes payment, Waffo Point Topup calls your API to fulfill the top-up, and the user never handles a point code.

<CardGroup cols={2}>
  <Card title="Advantages" icon="circle-check">
    * Seamless user experience, no point code needed
    * Instant account credit
    * Higher conversion rate
  </Card>

  <Card title="Use cases" icon="users">
    * Game publishers with an account top-up system
    * Membership and subscription services
  </Card>
</CardGroup>

## Integration flow

<Tabs>
  <Tab title="Simple flow">
    <Frame>
      <img src="https://mintcdn.com/waffo-docs/hUiobY-hbNbq3QYe/images/developer-docs/point-topup/mode-c-flow-simple.png?fit=max&auto=format&n=hUiobY-hbNbq3QYe&q=85&s=abc2f4eabfba9391ae31235d0407ff4c" alt="Mode C simple flow: the supplier builds a signed redirect link, the user pays on Waffo, and Waffo calls the supplier's fulfillment endpoint" width="3244" height="2933" data-path="images/developer-docs/point-topup/mode-c-flow-simple.png" />
    </Frame>
  </Tab>

  <Tab title="Detailed flow">
    <Frame>
      <img src="https://mintcdn.com/waffo-docs/hUiobY-hbNbq3QYe/images/developer-docs/point-topup/mode-c-flow-detailed.png?fit=max&auto=format&n=hUiobY-hbNbq3QYe&q=85&s=72a90991e77f8e9d54d5545498ae843c" alt="Mode C detailed sequence covering signature verification, session setup, payment, the fulfillment call, fulfillment inquiry, and webhook notification" width="4347" height="7001" data-path="images/developer-docs/point-topup/mode-c-flow-detailed.png" />
    </Frame>
  </Tab>
</Tabs>

## API list

| No. | API name                                                        | Description                                                                                                                                                   | Provider    | Integration                        |
| --- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ---------------------------------- |
| 1   | [URL signature and redirect](#url-signature-and-redirect)       | URL signature authentication                                                                                                                                  | Waffo       | **required**                       |
| 2   | [Direct fulfillment](#direct-fulfillment)                       | Create a direct fulfillment order                                                                                                                             | Supplier    | Optional integration (sample spec) |
| 3   | [Fulfillment inquiry](#fulfillment-inquiry)                     | Query a direct fulfillment order. Not needed if your top-up request is itself idempotent — Waffo retries with the same order ID and you guarantee idempotency | Supplier    | Optional integration (sample spec) |
| 4   | [Fulfillment result webhook](#fulfillment-result-webhook)       | Waffo pushes the final fulfillment status to the supplier                                                                                                     | Waffo sends | Optional                           |
| 5   | [Waffo fulfillment inquiry API](#waffo-fulfillment-inquiry-api) | Supplier pulls the fulfillment result from Waffo                                                                                                              | Waffo       | Optional                           |

<Note>
  The minimal integration needs only items 1 and 4: signed redirect plus the fulfillment result webhook, with no fulfillment endpoint of your own. In that case the webhook only reports `PAY_SUCCESS` and `PAYMENT_FAILED`, and you run your own delivery process after receiving `PAY_SUCCESS`.
</Note>

## URL signature and redirect

Passes user information through signed URL parameters, guaranteeing parameter integrity. This is a Waffo-provided endpoint.

**The full parameter list with types, requirement flags, and examples is in the [API Reference: signed redirect](/docs/api-reference/point-topup-redirect/signed-redirect-to-checkout).** This section covers how to compute the signature.

<Warning>
  **The signature must be generated on the supplier backend. Never expose `SECRET_KEY` to the front end.** Signature validity is controlled by `timestamp`; requests older than 2 hours are rejected.
</Warning>

URL format:

```text theme={null}
https://{supplier}.waffoplay.com/redirect?amount={amount}&currency={currency}&faceValue={faceValue}&notifyUrl={notifyUrl}&productId={productId}&returnUrl={returnUrl}&salesOrderId={salesOrderId}&siteCode={siteCode}&supplierId={supplierId}&supplierUserAccount={supplierUserAccount}&theme={theme}&timestamp={timestamp}&signature={signature}
```

`{supplier}` is the dedicated subdomain Waffo assigns to you during onboarding.

<Info>
  **A custom supplier-owned domain can be used instead**, as long as you point that domain at the Waffo endpoint. When you do, the base URL in the signing string must be that custom domain — the signature covers the base URL, so a domain mismatch fails verification.
</Info>

### Signature algorithm

<Steps>
  <Step title="Drop parameters">
    Exclude the `signature` parameter and **drop any parameter whose value is empty or blank**.
  </Step>

  <Step title="Sort">
    Sort the remaining parameters by key in ASCII ascending order.
  </Step>

  <Step title="Build the signing string">
    Build it as `{baseUrl}?key1=value1&key2=value2&...`. **The base URL — scheme + host + path, for example `https://supplier.waffoplay.com/redirect` — must be included.**
  </Step>

  <Step title="Calculate the signature">
    Compute HMAC-SHA256 over the signing string, using the shared `SECRET_KEY` as the HMAC key.
  </Step>

  <Step title="Convert to uppercase">
    Hex-encode and convert to uppercase.
  </Step>
</Steps>

### Signature example

Given these parameters:

| Parameter             | Value                                                  |
| --------------------- | ------------------------------------------------------ |
| `supplierUserAccount` | `supplier_user_12345`                                  |
| `supplierUserEmail`   | `test@supplier.com`                                    |
| `supplierId`          | `WAFFO_POINT_TOPUP_001`                                |
| `faceValue`           | `3000`                                                 |
| `amount`              | `3000`                                                 |
| `currency`            | `JPY`                                                  |
| `salesOrderId`        | `A123456`                                              |
| `siteCode`            | `US`                                                   |
| `returnUrl`           | `https://example.com/return_url_page?salesOrderId=XXX` |
| `theme`               | `light`                                                |
| `timestamp`           | `1713483091000`                                        |
| `SECRET_KEY`          | `your_secret_key_here`                                 |

Step 1, sort and concatenate:

```text theme={null}
https://supplier.waffoplay.com/redirect?amount=3000&currency=JPY&faceValue=3000&returnUrl=https://example.com/return_url_page?salesOrderId=XXX&salesOrderId=A123456&siteCode=US&supplierId=WAFFO_POINT_TOPUP_001&supplierUserAccount=supplier_user_12345&supplierUserEmail=test@supplier.com&theme=light&timestamp=1713483091000
```

Step 2, calculate HMAC-SHA256 and convert to uppercase:

```text theme={null}
signature = HMAC_SHA256(signing string).toUpperCase()
```

Complete URL:

```text theme={null}
https://supplier.waffoplay.com/redirect?amount=3000&currency=JPY&faceValue=3000&returnUrl=https%3A%2F%2Fexample.com%2Freturn_url_page%3FsalesOrderId%3DXXX&salesOrderId=A123456&siteCode=US&supplierId=WAFFO_POINT_TOPUP_001&supplierUserAccount=supplier_user_12345&supplierUserEmail=test@supplier.com&theme=light&timestamp=1713483091000&signature=ABC123XYZ456DEF789
```

<Tip>
  Note that `returnUrl` appears **unencoded** in the signing string but **URL-encoded** in the final URL. Signing and transport are separate steps; do not mix the two forms.
</Tip>

### Java implementation example

```java theme={null}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;

public class SignatureUtil {

    private static final String HMAC_SHA256 = "HmacSHA256";

    /**
     * Generate signature using HMAC-SHA256 algorithm (including base URL)
     *
     * @param baseUrl   base URL including host and path (e.g., https://supplier.waffoplay.com/redirect)
     * @param params    request parameters
     * @param secretKey secret key
     * @return signature string (uppercase hexadecimal)
     */
    public static String generateSignature(String baseUrl, Map<String, String> params, String secretKey) {
        try {
            // Use TreeMap for automatic alphabetical sorting
            TreeMap<String, String> sortedParams = new TreeMap<>(params);

            // Remove existing signature parameter if present
            sortedParams.remove("signature");

            // Build full URL with sorted parameters
            StringBuilder fullUrl = new StringBuilder(baseUrl);
            fullUrl.append("?");
            for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
                if (fullUrl.charAt(fullUrl.length() - 1) != '?') {
                    fullUrl.append("&");
                }
                fullUrl.append(entry.getKey())
                       .append("=")
                       .append(entry.getValue());
            }

            // Calculate signature using HMAC-SHA256 algorithm
            Mac mac = Mac.getInstance(HMAC_SHA256);
            SecretKeySpec secretKeySpec = new SecretKeySpec(
                secretKey.getBytes(StandardCharsets.UTF_8),
                HMAC_SHA256
            );
            mac.init(secretKeySpec);
            byte[] hash = mac.doFinal(fullUrl.toString().getBytes(StandardCharsets.UTF_8));

            // Convert to hexadecimal string and uppercase
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }

            return hexString.toString().toUpperCase();
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate HMAC-SHA256 signature", e);
        }
    }

    /**
     * Verify signature
     */
    public static boolean verifySignature(String baseUrl, Map<String, String> params, String secretKey) {
        String receivedSignature = params.get("signature");
        if (receivedSignature == null || receivedSignature.isEmpty()) {
            return false;
        }
        String calculatedSignature = generateSignature(baseUrl, params, secretKey);
        return calculatedSignature.equals(receivedSignature);
    }

    /**
     * Build the final signed URL
     */
    public static String buildSignedUrl(String baseUrl, Map<String, String> params, String secretKey) {
        TreeMap<String, String> sortedParams = new TreeMap<>(params);
        sortedParams.remove("signature");

        String signature = generateSignature(baseUrl, sortedParams, secretKey);
        sortedParams.put("signature", signature);

        StringBuilder url = new StringBuilder(baseUrl);
        url.append("?");
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            if (url.charAt(url.length() - 1) != '?') {
                url.append("&");
            }
            url.append(entry.getKey())
               .append("=")
               .append(entry.getValue());
        }

        return url.toString();
    }
}
```

<Card title="Node.js signing guide (HMAC-SHA256 and RSA-SHA256)" icon="file-archive" href="/docs/files/developer-docs/point-topup/waffoplay-sign-methods.zip">
  Download `Waffoplay Sign Methods.zip` for Node.js implementations of HMAC-SHA256 and RSA-SHA256 signing.
</Card>

### Verification flow

When the user accesses the signed URL, the Waffo Point Topup backend:

1. Receives all URL parameters
2. Validates that all required parameters are present: `faceValue`, `salesOrderId`, `supplierId`, `supplierUserAccount`, `siteCode`, `timestamp`, `signature`
3. Verifies that `timestamp` is within the valid period of 2 hours
4. Recalculates the signature using the same algorithm
5. Compares the calculated signature with the signature in the request
6. On success, generates a JWT token and sets it in the user session
7. Redirects to the target `siteCode` purchase page

Error handling:

| Condition                    | Result                                                               |
| ---------------------------- | -------------------------------------------------------------------- |
| Missing parameters           | Redirect to an error page indicating the missing required parameters |
| Signature verification fails | Redirect to an error page indicating an invalid signature            |
| Timestamp expired            | Redirect to an expiry page indicating the link has expired           |

## Direct fulfillment

Called by Waffo Point Topup after the user completes payment.

| Item           | Value                                                                                                                         |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Method         | `POST`                                                                                                                        |
| Path           | Defined by the supplier                                                                                                       |
| Authentication | Common API signature (SHA256WithRSA), see [API common specifications](/docs/en/developer-docs/point-topup/api-common#api-security) |

<Warning>
  The specification in this section is a **sample**. If you already have a direct top-up endpoint, hand your own API documentation to Waffo Point Topup — Waffo Point Topup adapts to your existing interface.
</Warning>

<Warning>
  **Idempotency requirement:** the supplier must support idempotency on `salesOrderId` + `supplierId`. If the same request arrives more than once, return the same success response.
</Warning>

<Tip>
  **Strongly recommended:** verify that `faceValue`, `amount`, and `currency` for the same `salesOrderId` are strictly consistent with the order record in your internal system. If that business validation fails, reject the fulfillment.
</Tip>

### Request

| Field name     |                       | Description                                                                                                                                        | Type          | Required |
| -------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------- |
| `supplierId`   |                       | Unique identifier assigned by Waffo to the supplier                                                                                                | String(64)    | required |
| `salesOrderId` |                       | Your order ID provided in the signed URL. Used with `supplierId` as the idempotent key                                                             | String(64)    | required |
| `faceValue`    |                       | Face value                                                                                                                                         | String(32)    | required |
| `amount`       |                       | Actual amount to be paid for the order. If you receive both **face value** and **order amount**, validating them against each other is recommended | DECIMAL(20,8) | optional |
| `currency`     |                       | Order pricing currency                                                                                                                             | String(3)     | optional |
| `requestedAt`  |                       | Waffo-side request time. ISO 8601 extended format in UTC, `YYYY-MM-DDThh:mm:ss.sssZ`                                                               | String(32)    | required |
| `buyerInfo`    |                       | Buyer information. Required in direct fulfillment scenarios                                                                                        | Object        | optional |
|                | `supplierUserAccount` | Charge account ID                                                                                                                                  | String(64)    | optional |

<Note>
  In `requestedAt`, `T` separates the date from the time, `.000` is milliseconds, and `Z` denotes UTC. For example `2025-01-05T10:30:00.000Z` is 10:30:00 UTC on 5 January 2025.
</Note>

### Response

| Field name     | Description                                                                                                | Type       | Required |
| -------------- | ---------------------------------------------------------------------------------------------------------- | ---------- | -------- |
| `salesOrderId` | Your order ID provided in the signed URL                                                                   | String(32) | required |
| `status`       | Order status: `IN_PROGRESS` purchase in progress, `SUCCESS` purchase successful, `FAILURE` purchase failed | String(24) | required |

Response example:

```json theme={null}
{
  "code": "0",
  "msg": "success",
  "data": {
    "salesOrderId": "M202504160311311156635",
    "status": "IN_PROGRESS"
  }
}
```

## Fulfillment inquiry

Called by Waffo Point Topup to query the fulfillment result from the supplier.

| Item           | Value                                                                                                                         |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Method         | `POST`                                                                                                                        |
| Path           | Defined by the supplier                                                                                                       |
| Authentication | Common API signature (SHA256WithRSA), see [API common specifications](/docs/en/developer-docs/point-topup/api-common#api-security) |

<Note>
  If your direct fulfillment endpoint is itself idempotent, you do not need this inquiry endpoint — Waffo retries with the same `salesOrderId` and you guarantee idempotent handling.
</Note>

### Request

| Field name     | Description                                         | Type       | Required |
| -------------- | --------------------------------------------------- | ---------- | -------- |
| `salesOrderId` | Your order ID provided in the signed URL            | String(64) | required |
| `supplierId`   | Unique identifier assigned by Waffo to the supplier | String(64) | required |

### Response

| Field name      |   | Description                                                          | Type       | Required |
| --------------- | - | -------------------------------------------------------------------- | ---------- | -------- |
| `salesOrderId`  |   | Your order ID provided in the signed URL                             | String(64) | required |
| `supplierId`    |   | Unique identifier assigned by Waffo to the supplier                  | String(64) | required |
| `status`        |   | Order status: `IN_PROGRESS`, `SUCCESS`, `FAILURE`                    | String(24) | required |
| `faceValue`     |   | Face value. Needed for game point code products with a dynamic value | String(32) | optional |
| `buyerInfo`     |   | Buyer information, same object structure as the fulfillment request  | Object     | optional |
| `requestedAt`   |   | Waffo-side request time, same as the fulfillment request             | String(32) | required |
| `completedTime` |   | Order completion time                                                | String(32) | optional |
| `failureCode`   |   | The order's failure code                                             | String(32) | optional |
| `failureReason` |   | The order's failure reason                                           | String(64) | optional |

Response example:

```json theme={null}
{
  "code": "0",
  "msg": "success",
  "data": {
    "salesOrderId": "M202504160311311156635",
    "supplierId": "123",
    "status": "SUCCESS",
    "faceValue": 1000,
    "buyerInfo": {
      "supplierUserAccount": "user@example.com"
    },
    "requestedAt": "2024-04-16T03:11:31.000Z",
    "completedTime": "2024-04-16T03:12:31.000Z"
  }
}
```

## Fulfillment result webhook

Waffo Point Topup posts the final fulfillment status to the endpoint you pass as the `notifyUrl` parameter on the signed redirect, covering payment success with fulfillment failure, payment failure, and unpaid orders that timed out.

**The full field list, the complete `failureCode` table, and request examples are in the [API Reference: fulfillment result webhook](/docs/api-reference/point-topup-fulfillment-webhook).**

<Warning>
  **Idempotency requirement:** the supplier must support idempotency on `salesOrderId` + `supplierId`. If the same notification arrives more than once, return the same success response.
</Warning>

`fulfillmentStatus` is what drives your integration logic:

| Value                       | Meaning                                                                                                                    |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `SUCCESS`                   | Fulfillment successful                                                                                                     |
| `PAY_SUCCESS`               | Payment successful. Sent to suppliers without a fulfillment API integration, as the trigger for their own delivery process |
| `PAY_SUCCESS_SUPPLY_FAILED` | Payment was successful but fulfillment failed. Confirmation required for refund or reissue                                 |
| `PAYMENT_FAILED`            | Payment failed, including user payment timeouts                                                                            |

<Note>
  If you choose the minimal integration — signed redirect plus this webhook, with no fulfillment endpoint — Waffo only reports `PAY_SUCCESS` and `PAYMENT_FAILED`.
</Note>

### Response and retry policy

After receiving the fulfillment callback, if you processed it successfully, respond with HTTP `200 OK` and include `success` in the response body. Waffo Point Topup then treats the fulfillment result as successfully notified. **Otherwise Waffo Point Topup retries.**

```json theme={null}
{
    "message": "success"
}
```

| Item                   | Value                                                             |
| ---------------------- | ----------------------------------------------------------------- |
| Retry intervals        | 5s, 30s, 1min, 5min, 30min, 1h, 2h, 4h, 8h, 24h, 24h…             |
| Maximum retry attempts | 15                                                                |
| After the maximum      | The notification is marked as failed and requires manual handling |

## Waffo fulfillment inquiry API

Lets the supplier query the fulfillment result of a Mode C order from Waffo Point Topup. This is the **pull-based counterpart** of the fulfillment result webhook, typically used for reconciliation or as a fallback when a webhook notification was missed. The response `data` structure is identical to the webhook `data`.

**Full parameters, response examples, and a live playground are in the [API Reference: fulfillment inquiry](/docs/api-reference/point-topup-fulfillment-inquiry/fulfillment-inquiry).**

| Item           | Value                                                                                                                         |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Method         | `POST`                                                                                                                        |
| Path           | `/api/v1/gamepin/fulfillment-inquiry`                                                                                         |
| Authentication | Common API signature (SHA256WithRSA), see [API common specifications](/docs/en/developer-docs/point-topup/api-common#api-security) |

Lookup keys: `supplierId` is required, and at least one of `salesOrderId` and `payOrderId` must be provided. If the order does not exist, or does not belong to the calling supplier, an error response is returned per the [error codes](/docs/en/developer-docs/point-topup/api-common#error-codes).

```json theme={null}
{
    "supplierId": "WAFFO_POINT_TOPUP_001",
    "salesOrderId": "A123456"
}
```

## Next steps

<CardGroup cols={2}>
  <Card title="API common specifications" icon="shield-check" href="/docs/en/developer-docs/point-topup/api-common">
    Message structure, RSA signing and verification, error codes, and key generation.
  </Card>

  <Card title="Integration overview" icon="map" href="/docs/en/developer-docs/point-topup/overview">
    Full mode comparison and the preparation checklist.
  </Card>
</CardGroup>
