> For the complete documentation index, see [llms.txt](https://help.dollarlabs.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.dollarlabs.io/dollarback-store-credit/earning-cashback-programs/custom-rewards-via-api.md).

# Custom rewards via API

{% embed url="<https://dollarlabs.neetorecord.com/watch/068497057337a7f7d2d1>" %}

*This article's section starts at 0:00 in the video.*

The **Custom (API)** program type lets you reward anything DollarBack can't see on its own: product reviews, UGC submissions, in-store visits, quiz completions. You define the reward in the admin; your server triggers it with one API call.

## Prerequisites

* A Merchant API key; generate one under **DollarBack admin → Settings → Developer API → REST API**. See [Merchant API: keys & endpoints](/dollarback-store-credit/developer-tools/merchant-api-keys-and-endpoints.md).
* A server to call the endpoint from. Never call the Merchant API from storefront JavaScript; the key must stay server-side.

## Step 1: Create the custom config

1. Open **DollarBack admin → Home**, select the **Cashback** tab and click **Manage configurations**. Choose the **Custom (API)** type.
2. Set the credit amount and optional start/end dates.
3. In the **API Reward** section, optionally check **Limit how many times each customer can earn this reward**, then set **Limit** (how many times), **Period** (day, month or year) and **Calculation method**:
   * **Rolling (N-day lookback)** counts the customer's rewards from this program in the last 1, 30 or 365 days.
   * **Calendar (period reset)** counts from the start of the current day, month or year.
4. Save and enable the config.
5. Find the config's ID: call [`GET /api/v1/merchant/config`](/dollarback-store-credit/developer-tools/merchant-api-keys-and-endpoints.md#get-api-v1-merchant-config) and take the `id` of the entry whose `configType` is `custom`. The admin doesn't display config IDs. The response is cached for about a minute, so a config you have just saved can take a minute to appear.

<figure><img src="https://2844543096-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcVBfwy7Yj4tU8TsxkHEr%2Fuploads%2Fgit-blob-eb8c5916ef06b061280c916d44cfa3c296e26fae%2Fdb-custom-api-reward-annotated.png?alt=media" alt="Custom reward editor with Amount $10, Credit expires 0 days, and an API Reward section with an unticked limit checkbox." width="560"><figcaption><p>A Custom (API) program pays only when your server calls the endpoint. You can limit how often one customer earns it.</p></figcaption></figure>

Custom rewards are granted **only** when you call the endpoint with this config's ID. They are never triggered by Shopify webhooks or storefront actions.

## Step 2: Call the endpoint

```
POST /api/v1/merchant/custom
```

| Header              | Value                      |
| ------------------- | -------------------------- |
| `X-DollarBack-Shop` | `your-store.myshopify.com` |
| `Authorization`     | `Bearer <your API key>`    |
| `Content-Type`      | `application/json`         |

| Body field       | Type             | Required | Meaning                                                                                                                             |
| ---------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `configId`       | string           | Yes      | The custom config's ID from Step 1                                                                                                  |
| `customerId`     | string or number | Yes      | The numeric Shopify customer ID                                                                                                     |
| `currency`       | string           | Yes      | Currency to credit in, e.g. `"USD"`                                                                                                 |
| `idempotencyKey` | string           | No       | A stable ID from your system (a review ID, an event ID). A retry with the same key joins the first grant instead of granting twice. |

Example:

```bash
curl -X POST "https://<app host>/api/v1/merchant/custom" \
  -H "X-DollarBack-Shop: your-store.myshopify.com" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "configId": "YOUR_CONFIG_ID",
    "customerId": "7345098912345",
    "currency": "USD",
    "idempotencyKey": "review-58213"
  }'
```

The exact request base URL, your key, and a copy-ready snippet are shown on the **REST API** page in the admin.

## Responses

| Status | Body                                                                                                                                                                                            | Meaning                                                                                                        |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| 200    | `{ "success": true, "requestId": "…", "rewardKind": "store_credit", "amount": 10, "currency": "USD", "transactionId": "…", "balance": 27.5, "formattedBalance": "$27.50", "duplicate": false }` | The credit has landed. The response waits for the outcome.                                                     |
| 200    | `{ "success": true, "requestId": "…", "rewardKind": "discount_code", "code": "…" }`                                                                                                             | The program pays a discount code; this is the code the customer received                                       |
| 200    | `{ "success": true, "requestId": "…", …, "duplicate": true }`                                                                                                                                   | A retry with an `idempotencyKey` that was already granted. Nothing moved again.                                |
| 200    | `{ "success": true, "requestId": "…", "skipped": true, "reason": "frequency_limit", "count": 3, "limit": 3 }`                                                                                   | The customer has reached the program's limit. Nothing moved.                                                   |
| 400    | `{ "error": "invalid_config", "reason": "not_found" \| "not_custom" \| "disabled" \| "not_scheduled" }`                                                                                         | The `configId` doesn't exist, isn't a Custom (API) config, is disabled, or is outside its date window          |
| 400    | `{ "error": "missing_fields: …" }` or `{ "error": "invalid_json" }`                                                                                                                             | Malformed request, including a missing `currency`                                                              |
| 400    | `{ "error": "operation_failed", "message": "…" }`                                                                                                                                               | Shopify refused the credit; the message is Shopify's own                                                       |
| 401    | `{ "error": "unauthorized" }`                                                                                                                                                                   | Wrong or missing API key / shop header                                                                         |
| 500    | `{ "error": "internal" }`                                                                                                                                                                       | Retry later                                                                                                    |
| 503    | `{ "error": "unavailable", "runId": "…" }`                                                                                                                                                      | The reward didn't finish within about 25 seconds and may still complete. Retry with the same `idempotencyKey`. |

{% hint style="warning" %}
Passing another config type's ID returns `400 invalid_config` with reason `not_custom`; only configs created as **Custom (API)** can be dispatched through this endpoint. Per-customer limits are enforced server-side: a customer at the limit gets `skipped: true` and isn't credited again.

Always send an `idempotencyKey` if your system might retry. Without one, a retried request after a timeout or a `503` can grant the reward a second time.
{% endhint %}

## Verify it works

1. Send the request for a test customer and confirm the `200` response with a `transactionId` (or a `code`).
2. The credit appears in **DollarBack admin → Analytics → Activity**, tagged with the config name.
3. The customer sees the balance in the widget and account, and receives the credited email if enabled.

## Common issues

* `401 unauthorized`: key revoked or headers wrong; regenerate under [Merchant API: keys & endpoints](/dollarback-store-credit/developer-tools/merchant-api-keys-and-endpoints.md).
* `200` with `skipped: true`: the customer has reached the program's limit. Raise the limit or turn it off in the **API Reward** section.
* `400 missing_fields: currency is required`: send `currency`. Older examples used `customerCurrency`, which is not read.
* Credit in the wrong currency: `currency` controls the credited currency; send the customer's own currency.

## Related articles

* [Merchant API: keys & endpoints](/dollarback-store-credit/developer-tools/merchant-api-keys-and-endpoints.md)
* [Cashback program types at a glance](/dollarback-store-credit/earning-cashback-programs/cashback-program-types-at-a-glance.md)
* [Set up birthday rewards](/dollarback-store-credit/earning-cashback-programs/set-up-birthday-rewards.md)
* [When credit is issued](/dollarback-store-credit/earning-cashback-programs/when-credit-is-issued.md)
