Credit & debit points

Learn how to manage the transaction lifecycle for purchases, returns, and bonuses.

Overview

Every credit or debit in the Loyalty API goes through a two-phase lifecycle:

  1. Create — the transaction is recorded with status pending. The member’s balance is not yet updated.
  2. Complete or Cancel — you explicitly confirm or void the operation.

This pattern mirrors real-world eCommerce flows: credit points only when a purchase is confirmed, not when it is merely placed — and cancel the credit if the order is cancelled or returned.

Step 1: Create transaction / balance order → status: PENDING (balance unchanged)
Step 2a: Complete → status: COMPLETED (balance updated)
Step 2b: Cancel → status: CANCELLED (balance unchanged)

Create a transaction

Endpoint: POST https://api.brevo.com/v3/loyalty/balance/programs/{pid}/transactions

Use this for non-purchase events: sign-up bonuses, birthday credits, manual adjustments, referral rewards.

$curl --request POST \
> --url https://api.brevo.com/v3/loyalty/balance/programs/27xxdd7a-.../transactions \
> --header 'api-key: YOUR_API_KEY' \
> --header 'content-type: application/json' \
> --data '{
> "contactId": 12345,
> "balanceDefinitionId": "a74cxx1d-4a96-4xx3-804e-dc3xxd9axxeb",
> "amount": 50,
> "autoComplete": false,
> "meta": {
> "reason": "signup_bonus"
> }
> }'

Request parameters

ParameterTypeRequiredDescription
contactIdintegerYes*Brevo contact ID (*required unless loyaltySubscriptionId is provided)
loyaltySubscriptionIdstringYes*Your external member ID (*required unless contactId is provided)
balanceDefinitionIdstringYesUUID of the balance definition to credit or debit
amountdoubleYesTransaction amount. Positive = credit; negative = debit. Must be non-zero.
autoCompletebooleanNoIf true, the transaction is immediately completed. If false (default), it stays pending until you call /complete.
eventTimestringNoISO 8601 timestamp of when the triggering event occurred (for backdated imports)
expiryBalanceMinutesintegerNoTime in minutes before the credited balance expires. Must be > 0 if provided.
ttlintegerNoTime-to-live for the pending transaction in minutes. Auto-voided if not completed or cancelled within this window. Must be > 0.
metaobjectNoArbitrary key-value metadata (e.g. reason, campaign_id)

Response (200)

1{
2 "id": "txn_aaa111-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
3 "amount": 50,
4 "balanceDefinitionId": "a74cxx1d-4a96-4xx3-804e-dc3xxd9axxeb",
5 "contactId": 12345,
6 "loyaltyProgramId": "27xxdd7a-af67-0020-ba65-19d60000a26e",
7 "status": "pending",
8 "createdAt": "2025-03-01T10:00:00.000Z",
9 "updatedAt": "2025-03-01T10:00:00.000Z",
10 "cancelledAt": null,
11 "completedAt": null,
12 "rejectReason": null,
13 "rejectedAt": null
14}

Always persist the id returned by Create transaction. You need it to call /complete or /cancel. If you lose it, you will need to query the transaction list to retrieve it.


Complete a transaction

Endpoint: POST https://api.brevo.com/v3/loyalty/balance/programs/{pid}/transactions/{tid}/complete

Completing a transaction finalizes the credit or debit: the member’s balance is updated, tiers are re-evaluated, and reward rules are checked.

$curl --request POST \
> --url https://api.brevo.com/v3/loyalty/balance/programs/27xxdd7a-.../transactions/txn_aaa111-.../complete \
> --header 'api-key: YOUR_API_KEY'

Path parameters

ParameterTypeRequiredDescription
pidstring (UUID)YesLoyalty program ID
tidstring (UUID)YesTransaction ID returned from the Create transaction call

Response (200)

1{
2 "id": "txn_aaa111-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
3 "amount": 50,
4 "balanceDefinitionId": "a74cxx1d-4a96-4xx3-804e-dc3xxd9axxeb",
5 "contactId": 12345,
6 "loyaltyProgramId": "27xxdd7a-af67-0020-ba65-19d60000a26e",
7 "status": "completed",
8 "completedAt": "2025-03-01T10:05:00.000Z",
9 "cancelledAt": null,
10 "rejectReason": null,
11 "rejectedAt": null
12}

What happens after completion:

  • Member’s balance is updated (e.g. 0 → 50 pts)
  • If tier trigger is real_time: tier thresholds are evaluated and the member may be upgraded
  • Webhook balance_value_updated is fired
  • If a tier threshold is crossed: webhook tier_association_updated is fired
  • If a reward rule matches: a voucher is attributed

When to call Complete:

ScenarioAction
Sign-up bonus — credit immediatelyCreate with autoComplete: true, or create then complete immediately
Purchase order confirmed / shippedComplete the pending transaction
Manual point adjustment (admin)Create then complete immediately

Cancel a transaction

Endpoint: POST https://api.brevo.com/v3/loyalty/balance/programs/{pid}/transactions/{tid}/cancel

Cancelling a transaction voids it entirely. The member’s balance is not affected.

$curl --request POST \
> --url https://api.brevo.com/v3/loyalty/balance/programs/27xxdd7a-.../transactions/txn_aaa111-.../cancel \
> --header 'api-key: YOUR_API_KEY'

Response (200)

1{
2 "id": "txn_aaa111-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
3 "amount": 50,
4 "status": "cancelled",
5 "cancelledAt": "2025-03-01T10:05:00.000Z",
6 "completedAt": null,
7 "rejectReason": null
8}

When to call Cancel:

ScenarioAction
Purchase order cancelled before shipmentCancel the pending transaction
Duplicate transaction detectedCancel the duplicate
Fraud or abuse detectedCancel the pending transaction
ttl is too short for your flowCancel and recreate with a longer TTL

Handling returns after delivery (balance already completed):

A completed transaction cannot be cancelled. Instead, create a new transaction with a negative amount (debit) and complete it.

$# Step 1: create debit for returned order
$--data '{
> "contactId": 12345,
> "balanceDefinitionId": "...",
> "amount": -100,
> "meta": { "reason": "return_order_789" }
>}'
$
$# Step 2: complete the debit
$POST .../transactions/{debit_tid}/complete

Create a balance order

Endpoint: POST https://api.brevo.com/v3/loyalty/balance/programs/{pid}/create-order

A balance order is the right tool for crediting points on a purchase. It creates a single tracked event tied to a business order, which you later complete or cancel as one unit.

$curl --request POST \
> --url https://api.brevo.com/v3/loyalty/balance/programs/27xxdd7a-.../create-order \
> --header 'api-key: YOUR_API_KEY' \
> --header 'content-type: application/json' \
> --data '{
> "contactId": 12345,
> "balanceDefinitionId": "a74cxx1d-4a96-4xx3-804e-dc3xxd9axxeb",
> "amount": 100,
> "source": "user",
> "dueAt": "2025-03-02T10:00:00.000Z",
> "meta": {
> "orderId": "order_789",
> "orderAmount": 89.99
> }
> }'

Request parameters

ParameterTypeRequiredDescription
contactIdintegerYesBrevo contact ID (must be ≥ 1)
balanceDefinitionIdstring (UUID)YesWhich balance to credit
amountdoubleYesPoints or cashback amount to credit. Must be non-zero.
sourcestringYes"engine" (system-triggered) or "user" (customer-facing action)
dueAtstringYesRFC 3339 timestamp: when this order is due to be processed
expiresAtstringNoOptional expiry for the order itself (RFC 3339)
metaobjectNoStore your order ID, cart total, or any custom metadata

Response (200)

1{
2 "id": "ord_xyz789-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
3 "transactionid": "txn_bbb222-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
4 "contactId": 12345,
5 "loyaltyProgramId": "27xxdd7a-af67-0020-ba65-19d60000a26e",
6 "balanceDefinitionId": "a74cxx1d-4a96-4xx3-804e-dc3xxd9axxeb",
7 "amount": 100,
8 "source": "user",
9 "dueAt": "2025-03-02T10:00:00.000Z",
10 "processedAt": null,
11 "createdAt": "2025-03-01T10:00:00.000Z",
12 "updatedAt": "2025-03-01T10:00:00.000Z"
13}

The order returns a transactionid. Use this to call /complete or /cancel when the purchase status is resolved.

Transaction vs. balance order: A direct transaction is for a single credit or debit event on a single balance. A balance order is intended for purchase flows where you want to track the originating business event (the order) independently from the balance operation.


Which endpoint to use?

ScenarioRecommended endpoint
Sign-up bonus, birthday credit, referral rewardCreate transaction with autoComplete: true
Purchase — credit on confirmationCreate balance order → Complete on confirmation
Purchase — customer cancelled before shippingCreate balance order → Cancel
Item returned after deliveryCreate transaction (negative amount) → Complete
Manual admin adjustmentCreate transaction → Complete immediately
Bulk import of historical pointsCreate transaction with eventTime backdated → Complete

Error handling

HTTP codeMeaningWhat to do
401Invalid or missing API keyCheck your api-key header
403Insufficient permissionsEnsure the Loyalty module is active
404Program or transaction not foundVerify {pid} and {tid} path parameters
422Unprocessable entityCommon causes: amount is 0; contactId doesn’t exist; transaction already completed or cancelled; program not published
500Internal server errorRetry with exponential backoff. If persistent, contact Brevo support.