Skip to Content
Developer HubGuides & TutorialsFreebetsPromo Codes

Promo Codes

A promo code is a short code that an operator attaches to a freebet offer and a pool. A bettor types the code into your app and instantly receives a freebet, with no manual distribution step.

Operators create codes in the backoffice or through the partner API (see the Promo codes section of the Third-Party integration).

How a code turns into a freebet

A promo code holds:

  • the offer whose settings the freebet gets (mechanic, odds limits, filters, validity period);
  • the pool the freebet is paid from, which also defines its network and currency;
  • the amount every activation gives, in base units of the pool’s token ("5000000" is 5 USDT on a 6-decimal token);
  • the activation limit, the total number of freebets the code can issue;
  • the expiry, the instant from which the code can no longer be activated.

When a bettor activates a code, an ordinary freebet is created from these parts: the offer’s settings, the code’s amount, the code’s pool, and the code recorded as the freebet’s campaign group. The campaign group is visible to partners through the promoCode field of the bonuses returned by the Third-Party list; the bettor-facing freebet carries no campaign field. It then appears in useBonuses / getBonuses and is used like any other freebet, see Use Freebets.

Codes are case-insensitive for bettors: a code is trimmed and upper-cased on every lookup. A code consists of A-Z, 0-9, - and _, and is 1 to 32 characters long.

Activate a code

Activation is a public request: it does not need a signature or a sign-in, only the code, the bettor’s address and your affiliate address.

Use the useActivatePromoCode hook. It activates the code for the connected wallet and refreshes that wallet’s bonus queries itself.

'use client' import { useState } from 'react' import { useActivatePromoCode } from '@azuro-org/sdk' const PromoCodeForm: React.FC = () => { const [ code, setCode ] = useState('') const { activate, isPending } = useActivatePromoCode({ affiliate: '0x...', // your affiliate address }) return ( <form onSubmit={(event) => { event.preventDefault() activate({ code }) }}> <input value={code} onChange={(event) => setCode(event.target.value)} /> <button type="submit" disabled={isPending}>Activate</button> </form> ) }

Rules

The affiliate must match the code’s pool

The affiliate you send must equal the address of the code’s pool (compared case-insensitively). A code created on another pool fails with bonus.promo_code_affiliate_mismatch for every bettor of your frontend, so check the pool when you create codes.

Code expiry and freebet validity

They are independent:

  • The code’s expiry only ends activations. It is non-inclusive: the code stops working the instant it is reached.
  • The freebet’s own expiry is the activation time plus the offer’s validity period. A bettor who activates a code just before it expires still gets the full period.
  • Expiring or deactivating a code never touches freebets that were already issued. Deactivation is final: a deactivated code cannot be reactivated.

One activation per address per code

An address can activate a given code once. A second attempt fails with bonus.promo_code_already_activated, even after the code has used up its activation limit.

No cooldown across codes

Nothing limits how many different codes one address can activate. If your product needs such a limit, enforce it on your side.

Activation limit

Once the number of issued freebets reaches the code’s activation limit, further activations fail with bonus.promo_code_limit_reached.

Failure reasons

Failed activations carry a reason in code. Branch on code, never on message.

CodeMeaningSuggested message
bonus.promo_code_not_foundNo such code, including any code with characters other than A-Z, 0-9, -, _The code doesn’t exist
bonus.promo_code_deactivatedThe operator deactivated the codeThe code is no longer active
bonus.promo_code_expiredThe code’s expiry has been reachedThe code has expired
bonus.promo_code_unavailableThe code’s product or operator is not activeThe code is not available right now
bonus.promo_code_affiliate_mismatchThe code’s pool address is not the affiliate you sentThe code is not valid on this site
bonus.promo_code_already_activatedThis address already activated this codeThe code was already used
bonus.promo_code_limit_reachedThe code has used up its activation limitAll activations of this code are used up
bonus.promo_code_busyAnother activation of the same code is being processedTry again in a moment
bonus.activate_promo_code_errorThe activation failed on the server for another reasonSomething went wrong, try again later
unknownAnything else: input rejected by validation (an empty code, a code longer than 32 characters, a malformed address), a server error, a reason this toolkit version does not know, or a successful response whose body cannot be readGeneric error

The checks run in this order: deactivated, expired, unavailable, affiliate mismatch, already activated, limit reached. So an address that already activated a used-up code gets bonus.promo_code_already_activated, not bonus.promo_code_limit_reached.

bonus.promo_code_busy is safe to retry. unknown also carries the HTTP status of the response, when there was one.

ℹ️

With the toolkit function, only a network failure is not a PromoCodeError: the rejected request propagates unchanged. An unreadable successful response is reported as unknown. Always check with isPromoCodeError(error) and fall back to a generic message, see activatePromoCode. The SDK hook reports a network failure as unknown instead, see useActivatePromoCode.

Keep the messages in a Record<PromoCodeErrorCode, string> map, so a reason added in a later toolkit version becomes a type error instead of a silently missing message:

import { isPromoCodeError, type PromoCodeErrorCode } from '@azuro-org/toolkit' const messages: Record<PromoCodeErrorCode, string> = { 'bonus.promo_code_not_found': 'The code doesn\'t exist', 'bonus.promo_code_deactivated': 'The code is no longer active', 'bonus.promo_code_expired': 'The code has expired', 'bonus.promo_code_unavailable': 'The code is not available right now', 'bonus.promo_code_affiliate_mismatch': 'The code is not valid on this site', 'bonus.promo_code_already_activated': 'The code was already used', 'bonus.promo_code_limit_reached': 'All activations of this code are used up', 'bonus.promo_code_busy': 'Try again in a moment', 'bonus.activate_promo_code_error': 'Something went wrong, try again later', 'unknown': 'Something went wrong', } const getErrorMessage = (error: unknown) => { return isPromoCodeError(error) ? messages[error.code] : 'Something went wrong' }