# ConvertPlus Buy-Link signature for catalog products

## Overview

Use signed buy-links to override the configured price of a catalog product at checkout. The signature prevents tampering with the price and other parameters passed as query strings.

The buy link signature is an HMAC-SHA256 hash computed from a subset of URL parameters using your **\*\*Buy Link Secret Word\*\*** (you can find this in the Merchant Control Panel) as the key. Any parameter listed as *\*signed\** below must be included in the hash if it is present in the URL.&#x20;

{% hint style="info" %}
The `merchant` parameter is never signed.
{% endhint %}

## Buy-link URL format

The buy-link URL should have the following format:

```markdown
https://secure.2checkout.com/checkout/buy?merchant=MERCHANT_CODE&prod=PRODUCT_CODE&qty=QTY&price=CURRENCY:AMOUNT&currency=CURRENCY&signature=SIGNATURE
```

## Parameters

<table><thead><tr><th width="165">Parameter</th><th width="131.00006103515625">Required / Optional</th><th width="104">Signed</th><th>Description</th></tr></thead><tbody><tr><td><code>merchant</code></td><td>Required</td><td>No</td><td>Your merchant code.</td></tr><tr><td><code>prod</code></td><td>Required</td><td>Yes</td><td>Catalog product code. Multiple products: semicolon-separated (`prod=CODE1;CODE2`).</td></tr><tr><td><code>qty</code></td><td>Required</td><td>Yes</td><td>Quantity. Multiple products: semicolon-separated (`qty=1;2`).</td></tr><tr><td><code>price</code></td><td>Required</td><td>Yes</td><td>Price override. See <a href="#price-format">Price Format</a> below.</td></tr><tr><td><code>currency</code></td><td>Required</td><td>Yes</td><td>ISO 4217 currency code (e.g. `USD`, `EUR`).</td></tr><tr><td><code>opt</code></td><td>Optional</td><td>Yes</td><td>Pricing option codes, semicolon-separated.</td></tr><tr><td><code>coupon</code></td><td>Optional</td><td>Yes</td><td>Coupon code.</td></tr><tr><td><code>lock</code></td><td>Optional</td><td>Yes</td><td>Set to `1` to prevent the customer from modifying the cart.</td></tr><tr><td><code>return-url</code></td><td>Optional</td><td>Yes</td><td>Redirect URL after a successful purchase. Use the <strong>unencoded</strong> URL when computing the signature.</td></tr><tr><td><code>return-type</code></td><td>Optional</td><td>Yes</td><td>Redirect method: `redirect` (header redirect) or `link` (link on thank-you page).</td></tr><tr><td><code>expiration</code></td><td>Optional</td><td>Yes</td><td>UTC Unix timestamp after which the link is no longer valid.</td></tr><tr><td><code>order-ext-ref</code></td><td>Optional</td><td>Yes</td><td>External order reference.</td></tr><tr><td><code>customer-ref</code></td><td>Optional</td><td>Yes</td><td>Numeric customer ID.</td></tr><tr><td><code>customer-ext-ref</code></td><td>Optional</td><td>Yes</td><td>External customer reference (e.g. email).</td></tr></tbody></table>

## Price format

For catalog products, the \`price\` parameter must embed the currency code as a prefix: price=USD:100

For multiple currencies on the same product, separate pairs with a comma: price=USD:100,EUR:90,GBP:80.

For multiple products, separate per-product price definitions with a semicolon: price=USD:100,EUR:90;USD:50,EUR:45.

{% hint style="warning" %}
Using a plain numeric value (e.g. \`price=100\`) without the currency prefix will result in an **Empty cart** error.

`renewal-price` is not a valid parameter for catalog products. It is only available for dynamic products (`dynamic=1`). To set a renewal price for a subscription catalog product, you must configure it in the product settings in the Merchant Control Panel.
{% endhint %}

## Signature algorithm

Follow these steps to generate the signature:

1. Collect signed parameters - Take only the parameters marked as \*Signed\* that are present in your URL. Skip any that you are not using.
2. Sort alphabetically by parameter name: currency, expiration, lock, opt, order-ext-ref, price, prod, qty, return-type, return-url, etc.
3. Serialize each value by prepending the character length of the value to the value itself:<br>

   | Value      | Serialized   |
   | ---------- | ------------ |
   | USD        | 3USD         |
   | USD:100    | 7USD:100     |
   | VQHKBLQNXW | 10VQHKBLQNXW |
   | 1          | 11           |
4. Concatenate all serialized values by joining them into a single string (no separator).
5. Compute HMAC-SHA256 by using your Buy Link Secret Word as the key.
6. Append to URL - Add the resulting 64-character hex string as the \`signature\` parameter.

## Example

### Parameters

| Parameter  | Value      | Signed |
| ---------- | ---------- | ------ |
| `merchant` | 2COLRNC    | No     |
| `prod`     | E2932D0DE2 | Yes    |
| `qty`      | 1          | Yes    |
| `price`    | USD:100    | Yes    |
| `currency` | USD        | Yes    |

### Step-by-step

1. Signed parameters, sorted alphabetically:\
   \
   **currency = USD**\
   **price = USD:100**\
   **prod = E2932D0DE2**\
   **qty = 1**
2. Serialized values:\
   \
   **USD → 3USD**\
   **USD:100 → 7USD:100**\
   **E2932D0DE2 → 10E2932D0DE2**\
   **1 → 11**
3. Concatenated string: **3USD7USD:10010E2932D0DE211**
4. HMAC-SHA256 (with secret word \`secret\_word\`): **<64-character hex signature>**.
5. Final URL:

```markdown
https://secure.2checkout.com/checkout/buy?merchant=2COLRNC&prod=E2932D0DE2&qty=1&price=USD:100&currency=USD&signature=<signature> 
```

## Example with general parameters

| Parameter       | Value                     | Signed |
| --------------- | ------------------------- | ------ |
| **merchant**    | 2COLRNC                   | No     |
| **prod**        | E2932D0DE2                | Yes    |
| **qty**         | 1                         | Yes    |
| **price**       | USD:100                   | Yes    |
| **currency**    | USD                       | Yes    |
| **return-url**  | <https://www.example.com> | Yes    |
| **return-type** | redirect                  | Yes    |
| **expiration**  | 1893456000                | Yes    |

Sorted signed parameters: currency, expiration, price, prod, qty, return-type, return-url.

Concatenated serialized string: 3USD101893456000 7USD:10010E2932D0DE2118redirect22[https://www.example.com](https://www.example.com/).

{% hint style="info" %}
For \`return-url\`, use the raw, unencoded URL in the signature even if it is percent-encoded in the final URL.
{% endhint %}

### Code sample

#### Python

````python
```python
import hmac
import hashlib

merchant = "YOUR_MERCHANT_CODE"
secret   = "YOUR_BUY_LINK_SECRET_WORD"

# Add only the parameters you are using
params = {
    "currency": "USD",
    "price":    "USD:100",   # must use CURRENCY:AMOUNT format
    "prod":     "YOUR_PRODUCT_CODE",
    "qty":      "1",
    # "return-url":  "https://yoursite.com/thank-you",
    # "return-type": "redirect",
    # "expiration":  "1893456000",
}

# Sort alphabetically, serialize, concatenate
serialized = "".join(str(len(v)) + v for _, v in sorted(params.items()))

# HMAC-SHA256
signature = hmac.new(
    secret.encode("utf-8"),
    serialized.encode("utf-8"),
    hashlib.sha256
).hexdigest()

url = (
    f"https://secure.2checkout.com/checkout/buy"
    f"?merchant={merchant}"
    f"&prod={params['prod']}"
    f"&qty={params['qty']}"
    f"&price={params['price']}"
    f"&currency={params['currency']}"
    f"&signature={signature}"
)
````

#### Javascript - (Browser — Web Crypto API)

```javascript
javascript
async function generateBuyLink({ merchant, secret, prod, qty, price, currency }) {
  const params = { currency, price, prod, qty };

  const sorted = Object.entries(params).sort(([a], [b]) => a.localeCompare(b));
  const serialized = sorted.map(([, v]) => String(v.length) + v).join("");

  const enc = new TextEncoder();
  const key = await crypto.subtle.importKey(
    "raw", enc.encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false, ["sign"]
  );
  const sig = await crypto.subtle.sign("HMAC", key, enc.encode(serialized));
  const signature = Array.from(new Uint8Array(sig))
    .map(b => b.toString(16).padStart(2, "0")).join("");

  const query = new URLSearchParams({ merchant, prod, qty, price, currency, signature });
  return `https://secure.2checkout.com/checkout/buy?${query}`;
}
```

## Validating your signature

Use the [Signature Generation API endpoint](/shopping-carts/convertplus/how-to-use-2checkout-signature-generation-api-endpoint.md) to generate a signature server-side and compare it against your own implementation. The API uses the Buy Link Secret Word configured in your merchant account.

````bash
```bash
curl -X POST https://secure.2checkout.com/checkout/api/encrypt/generate/signature \
  -H "Content-Type: application/json" \
  -H "merchant-token: YOUR_JWT" \
  -d '{
    "merchant": "YOUR_MERCHANT_CODE",
    "currency": "USD",
    "products": [
      {
        "code": "YOUR_PRODUCT_CODE",
        "quantity": 1,
        "custom-price": { "USD": 100 }
      }
    ]
  }'
````

## Troubleshooting

| Error                 | Cause                                         | Fix                                                |
| --------------------- | --------------------------------------------- | -------------------------------------------------- |
| Empty cart            | Invalid signature                             | Verify serialized string.                          |
| Empty cart            | Wrong price format (\`price=100\`)            | Use \`price=USD:100\`.                             |
| Empty cart            | \`renewal-price\` used with a catalog product | Remove it; set it in the Merchant Control Panel    |
| Empty cart            | Wrong secret key                              | Use Buy Link Secret Word, not API/INS key.         |
| Wrong price displayed | Currency mismatch                             | Match \`currency\` param with prefix in \`price\`. |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.2checkout.com/shopping-carts/convertplus/convertplus-buy-link-signature-for-catalog-products.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
