Recording Activities
Overview
The POST /api/v1/partner/activities endpoint records a customer activity and triggers the reward processing pipeline. Each time a member completes a transaction in your system, submit it to Otonum so the platform can evaluate active campaigns, calculate rewards, and update the member's balance.
Activities come in four types — Purchase, Transfer, Visit, and Review — each supporting an optional detail object that provides enriched, type-specific context.
For the full request and response contract, refer to the API Reference.
Core Fields
| Field | Required | Constraints | Description |
|---|---|---|---|
personalCode | Yes | Non-empty | Identifies the member performing the activity |
type | Yes | Purchase, Transfer, Visit, Review | The activity type |
amount | Conditional | Required and > 0 for Purchase/Transfer; must be 0 for Visit/Review | The monetary amount (e.g., transaction total, transfer amount) |
reference | Yes | Non-empty | Your internal unique identifier — used for deduplication. Same reference is never processed twice |
occurredAt | No | Max 5 minutes in the future | UTC timestamp when the activity occurred (ISO 8601). Defaults to submission time if omitted |
redemptionCode | No | — | Voucher code to redeem as part of this activity |
redemptionAmount | No | — | Discount amount applied via the voucher |
description | No | — | Free-text context about the activity |
detail | No | Must match the shape for the given type | Structured object specific to the activity type — see below |
Core field validation errors
| Field | Rule | Error Code |
|---|---|---|
personalCode | Non-empty | PERSONAL_CODE_REQUIRED |
reference | Non-empty | REFERENCE_REQUIRED |
type | Must be purchase, transfer, visit, or review | ACTIVITY_TYPE_INVALID |
occurredAt | Cannot be more than 5 minutes in the future | OCCURRED_AT_IN_FUTURE |
amount | Required and > 0 for Purchase/Transfer | AMOUNT_REQUIRED / AMOUNT_MUST_BE_POSITIVE |
amount | Must be 0 for Visit/Review | AMOUNT_NOT_APPLICABLE |
detail | JSON must be valid | DETAILS_INVALID_FORMAT |
detail | Cannot be null when required by type | DETAILS_NULL |
The detail Field
The detail property accepts a structured object whose shape depends on type. It is optional in all cases but strongly recommended — detail data feeds product-level insights, segmentation, and advanced campaign rules in Otonum.
The detail object must match the shape for its type. Sending a mismatched structure will result in a validation error.
Purchase
Use PurchaseDetails when type is Purchase.
| Property | Type | Required | Constraints | Description |
|---|---|---|---|---|
channel | PurchaseChannel | Yes | — | Sales channel: Instore, Online, USSD, or Whatsapp |
items | PurchaseItem[] | No | Max 500 items | List of purchased items for product-level insights |
terminalId | string | No | Max 50 characters | ID of the cash register or POS terminal |
PurchaseItem fields:
| Property | Type | Required | Constraints | Description |
|---|---|---|---|---|
key | string | No | — | Unique item key / SKU |
name | string | Yes | 1–200 chars | Item name or description |
quantity | integer | Yes | Min 1 | Quantity ordered |
unitPrice | number | Yes | Min 0 | Unit price |
Validation errors
| Field | Rule | Error Code |
|---|---|---|
channel | Required, valid enum value | PURCHASE_CHANNEL_INVALID |
terminalId | Max 50 characters | PURCHASE_TERMINAL_ID_TOO_LONG |
items | Max 500 entries | PURCHASE_ITEMS_TOO_MANY |
name | Non-empty | PURCHASE_ITEM_NAME_REQUIRED |
quantity | Minimum 1 | PURCHASE_ITEM_QUANTITY_INVALID |
unitPrice | Minimum 0 | PURCHASE_ITEM_PRICE_NEGATIVE |
Code
Transfer
Use TransferDetails when type is Transfer.
| Property | Type | Required | Constraints | Description |
|---|---|---|---|---|
originCountry | string | Yes | Exactly 2 chars, ISO 3166-1 | ISO 3166-1 alpha-2 origin country code (e.g., CI, SN, ML) |
destinationCountry | string | Yes | Exactly 2 chars, ISO 3166-1 | ISO 3166-1 alpha-2 destination country code |
operator | string | No | — | Mobile bank or Money operator (e.g., Orange Money, Wave, MTN MoMo) |
recipientPhone | string | No | International phone format | Recipient phone number — anonymized in public insights |
fees | number | No | Min 0 | Transfer fees charged to the member |
feesCurrency | string | No | 3 uppercase letters | ISO 4217 currency code for the fees (e.g., XOF, GHS) |
Validation errors
| Field | Rule | Error Code |
|---|---|---|
originCountry | Required | TRANSFER_ORIGIN_COUNTRY_REQUIRED |
originCountry | 2-letter ISO 3166-1 code | TRANSFER_ORIGIN_COUNTRY_INVALID |
destinationCountry | Required | TRANSFER_DESTINATION_COUNTRY_REQUIRED |
destinationCountry | 2-letter ISO 3166-1 code | TRANSFER_DESTINATION_COUNTRY_INVALID |
recipientPhone | International format (+?[1-9]\d{6,14}) | TRANSFER_RECIPIENT_PHONE_INVALID |
fees | Minimum 0 | TRANSFER_FEES_NEGATIVE |
feesCurrency | Exactly 3 uppercase letters (ISO 4217) | TRANSFER_FEES_CURRENCY_INVALID |
Code
Visit
Use VisitDetails when type is Visit.
| Property | Type | Required | Constraints | Description |
|---|---|---|---|---|
source | VisitSource | Yes | — | Check-in source: QrCode, Nfc, Manual, or App |
locationId | string | No | — | Location identifier — useful for multi-site loyalty programs |
locationName | string | No | Max 200 characters | Display name of the visited location |
Validation errors
| Field | Rule | Error Code |
|---|---|---|
source | Required | VISIT_SOURCE_REQUIRED |
source | Must be QrCode, Nfc, Manual, or App | VISIT_SOURCE_INVALID |
locationName | Max 200 characters | VISIT_LOCATION_NAME_TOO_LONG |
Code
Review
Use ReviewDetails when type is Review.
| Property | Type | Required | Constraints | Description |
|---|---|---|---|---|
rating | integer | Yes | 1–5 | Rating on a scale of 1 to 5 |
platform | string | No | Max 50 characters | Platform where the review was submitted (e.g., Google, inapp, WhatsApp) |
hasComment | boolean | No | — | true if the review includes a written comment. The text is not stored (GDPR) |
Validation errors
| Field | Rule | Error Code |
|---|---|---|
platform | Max 50 characters | REVIEW_PLATFORM_TOO_LONG |
Code
Asynchronous Processing
The activity endpoint returns a taskId immediately. Otonum processes rewards asynchronously:
- Evaluates the activity against all active campaigns for the application
- Evaluates rules for each eligible campaign
- All matching campaigns grant their rewards — they accumulate
- Any redeemed voucher is marked as consumed
- The member's balance is updated
- The member is notified according to your notification settings
Your checkout flow is never blocked waiting for Otonum to complete. For most integrations, explicit polling is unnecessary. See Task Status for scenarios where polling is relevant.
Key Points
referenceis your deduplication key — always use your internal order/transaction IDamountis required and must be> 0forPurchaseandTransfer; it must be0forVisitandReviewdetailis optional but recommended for all types; it enables richer campaign targeting- A
VisitorReviewwith no matching campaign still succeeds — no error is returned
Validation Error Reference
| Error Code | Message |
|---|---|
PERSONAL_CODE_REQUIRED | Personal code is required. |
REFERENCE_REQUIRED | Reference is required. |
ACTIVITY_TYPE_INVALID | Unknown activity type. Accepted values: purchase, transfer, visit, review. |
OCCURRED_AT_IN_FUTURE | Activity date cannot be in the future. |
AMOUNT_REQUIRED | Amount is required for this activity type. |
AMOUNT_MUST_BE_POSITIVE | Amount must be greater than zero. |
AMOUNT_NOT_APPLICABLE | Amount is not applicable for this activity type. |
DETAILS_INVALID_FORMAT | Details JSON format is invalid. |
DETAILS_NULL | Details cannot be null. |
PURCHASE_CHANNEL_INVALID | Invalid sales channel. Accepted values: InStore, Online, Ussd, WhatsApp. |
PURCHASE_TERMINAL_ID_TOO_LONG | Terminal ID cannot exceed 50 characters. |
PURCHASE_ITEMS_TOO_MANY | Number of items cannot exceed 500 per activity. |
PURCHASE_ITEM_NAME_REQUIRED | Item name is required. |
PURCHASE_ITEM_QUANTITY_INVALID | Item quantity must be greater than zero. |
PURCHASE_ITEM_PRICE_NEGATIVE | Unit price cannot be negative. |
TRANSFER_ORIGIN_COUNTRY_REQUIRED | Origin country is required. |
TRANSFER_ORIGIN_COUNTRY_INVALID | Invalid origin country. 2-letter ISO 3166-1 code required (e.g. CI). |
TRANSFER_DESTINATION_COUNTRY_REQUIRED | Destination country is required. |
TRANSFER_DESTINATION_COUNTRY_INVALID | Invalid destination country. 2-letter ISO 3166-1 code required (e.g. SN). |
TRANSFER_CORRIDOR_MISMATCH | Corridor does not match origin and destination countries. |
TRANSFER_RECIPIENT_PHONE_INVALID | Invalid recipient phone number format. |
TRANSFER_FEES_NEGATIVE | Transfer fees cannot be negative. |
TRANSFER_FEES_CURRENCY_INVALID | Invalid fees currency code. 3-letter ISO 4217 format required. |
VISIT_SOURCE_REQUIRED | Visit source is required. |
VISIT_SOURCE_INVALID | Invalid visit source. Accepted values: QrCode, Nfc, Manual, App. |
VISIT_LOCATION_NAME_TOO_LONG | Location name cannot exceed 200 characters. |
REVIEW_PLATFORM_TOO_LONG | Platform name cannot exceed 50 characters. |
See Also
- Campaigns: How activities trigger campaign evaluation
- Vouchers: Redemption lifecycle
- Task Status: Asynchronous tracking
- Integration Guide: End-to-end integration flow
