Introduction
Welcome to the Merit Points API.
This API enables partners to seamlessly integrate with Merit's Points platform, providing a comprehensive set of services for both Points Exchange and Points Earn. Through these APIs, partners can award points to customers and enable the exchange across supported loyalty programs.
The API is designed to support secure, scalable, and real-time integrations, allowing partners to deliver a seamless loyalty experience while leveraging Merit's extensive ecosystem.
Base URL
Make sure to replace
{{BaseURL}}with the relevant staging or production URL.
Base URL for Merit API endpoints
| Description | URL |
|---|---|
| Staging | https://api.sandbox.meritincentives.com/points-exchange |
| Prod | |
| Content-Type | application/json |
| Authorization | Bearer <TOKEN> |
Authentication
Sample API call with the Authorization header
curl "{{BaseURL}}/path/to/resource" \
-H "Authorization: Bearer <TOKEN>"
Make sure to replace
with your Token.
All the API requests to the server should include Token in the Authorization header:
Authorization: Bearer <TOKEN>
You must replace TOKEN with your Token.
Token will be provided while onboarding.
Authorization (Client Credentials Flow)
This API uses OAuth 2.0 Client Credentials Grant for authentication.
Overview
Your server first obtains a Bearer access token from the Token API using its assigned client_id and client_secret.
Include the returned token in the Authorization header of every API request:
Authorization: Bearer <TOKEN>
Each access token is valid for the number of seconds returned in expires_in, currently 86400 seconds (24 hours).
Request a new token before the current token expires.
OAuth Base URL
| Description | URL |
|---|---|
| Staging | https://api.sandbox.meritincentives.com |
| Production | https://api.meritincentives.com |
| Content-Type | application/x-www-form-urlencoded |
Token Endpoint
POST {{IdentityBaseURL}}/identity/v1/oauth2/token
Used to obtain the access token.
Headers
| Header | Relevance | Value |
|---|---|---|
| Content-Type | Required | application/x-www-form-urlencoded |
Body Parameters
| Parameter | Relevance | Value |
|---|---|---|
| grant_type | Required | Must be set to client_credentials. |
| client_id | Required | The client ID issued to your application, for example 4f9xxxx8a1e3f6xxxxxx. |
| client_secret | Required | The client secret issued to your application, for example Sxxx8v9Gxxxx. |
CURL REQUEST
curl -X POST "{{IdentityBaseURL}}/identity/v1/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=<CLIENT_ID>" \
--data-urlencode "client_secret=<CLIENT_SECRET>"
Response
On success, the endpoint returns HTTP 200 with:
| Key | Type | Description |
|---|---|---|
| access_token | string | Access token used to authenticate API requests. |
| token_type | string | Token type; always bearer. |
| expires_in | integer | Token validity period in seconds. |
RESPONSE - 200 OK
{
"access_token": "NgCXRKc...MzYjw",
"token_type": "bearer",
"expires_in": 86400
}
Client credentials are environment-specific. Store client_secret only in a secure server-side secret store. Never
include it in browser applications, mobile applications, POS logs, URLs, or API requests other than the Token Endpoint.
Common API Reference
List my loyalty programs
GET {host}/api/v1/loyalty-programs?limit=10&offset=0
This API lists all the loyalty programs that (usually) belong to the API Client or that have given direct earn/burn access to the API client. Usually, the length of the response list is not more than 1.
Headers
| Key | Value |
|---|---|
| Accept-Language | en |
| Authorization | Bearer <TOKEN> |
Possible error codes
- UNAUTHORIZED (401)
- ACCESS_DENIED (403)
- METHOD_NOT_ALLOWED (405)
- INTERNAL_ERROR (500)
Response:
Loyalty programs JSON structured like this:
[
{
"loyalty_program_id": "TOPSHERPA",
"loyalty_program_name": "TopSherpa",
"currency_id": "TOPSHERPA",
"currency_name": "Top Sherpa Coins",
"logo_url": "logo.com",
"banner_url": "banner.com",
"required_fields": [
{
"id": "id",
"name": "Member ID"
}
],
"validation_assurance": "CONFIRMED",
"description": "",
"terms_and_conditions": "",
"category": "Airline",
"brand": "TopSherpa",
"is_default": true
}
]
Loyalty Program
| Field | Description | Type |
|---|---|---|
| loyalty_program_id | Loyalty program slug | string |
| loyalty_program_name | Loyalty program name | string |
| currency_id | Currency slug used for exchange | string |
| currency_name | Currency name | string |
| logo_url | Loyalty program logo | string |
| banner_url | Loyalty program banner | string |
| category | Loyalty program category | string |
| brand | Loyalty program brand | string |
| description | Loyalty program description | string |
| terms_and_conditions | Loyalty program T&C | string |
| required_fields | Required fields to create exchange. Supported Member Fields | object |
| validation_assurance | Validation reliability: CONFIRMED, PROBABLE, RULE_BASED, or NONE |
string |
Member Validation
Member validation is shared by the Points Exchange and EARN APIs. It supports loyalty programs available through
either POINTS_TO_POINTS or FIAT_TO_POINTS partnerships.
POST {host}/api/v1/validate/member
Headers
| Key | Value |
|---|---|
| Authorization | Bearer <TOKEN> |
Request body
Validate member Request JSON structured like this:
{
"currency_id": "MERIT-COIN",
"member": {
"id": "member1",
"first_name": "Example first name",
"last_name": "Example last name",
"mobile_number": "123456789",
"country_calling_code": "123",
"email": "test@email.com"
}
}
| Field | Description | Type | Required |
|---|---|---|---|
| currency_id | Currency ID. | string | True |
| member | The required fields depend on the partner and are returned by List Exchange Out Loyalty Programs or List EARN Partners. For example, if a program requires only id and last_name, those fields are mandatory and the others are optional. Supported Member Fields |
object | True |
Possible error codes
- VALIDATION_ERROR (400)
- UNAUTHORIZED (401)
- ACCESS_DENIED (403)
- HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM (403)
- NOT_FOUND (404)
- METHOD_NOT_ALLOWED (405)
- INTERNAL_ERROR (500)
Response 200
Validate member JSON structured like this:
{
"valid": true,
"status": "VALID"
}
| Field | Description | Type |
|---|---|---|
valid |
Backward-compatible boolean result derived from status |
boolean |
status |
Validation outcome: VALID, INVALID, UNABLE_TO_REACH, or NOT_SUPPORTED |
string |
Validation Outcomes
| Status | valid |
Meaning |
|---|---|---|
VALID |
true |
Validation completed and the partner or configured rules accepted the member |
INVALID |
false |
Validation completed and the member was rejected, not found, ineligible, or blocked |
UNABLE_TO_REACH |
false |
Online validation could not reach the partner or receive a usable response |
NOT_SUPPORTED |
true |
Pre-transaction validation is unavailable; the transaction may proceed and performs in-place validation |
Supported Member Fields
| Field | Description | Type |
|---|---|---|
| id | The unique loyalty membership identifier | string |
| first_name | The first name used by the member to register with the loyalty program | string |
| last_name | The last name used by the member to register with the loyalty program | string |
| mobile_number | The mobile number used by the member to register with the loyalty program. This is without the country code prefix. Example: "5555555555". Country code should be passed separately under country_calling_code. | string |
| country_calling_code | The country code of the mobile number. Example: "966" (Without a '+') | string |
| The email used by the member to register with the loyalty program | string |
Validation Assurance
The loyalty program's validation_assurance communicates how much confidence clients should place in VALID or
INVALID. Merit forwards decisions returned by partners and integration aggregators without increasing their
assurance. A transaction may still fail even after VALID, because posting performs its own in-place validation.
validation_assurance |
VALID |
INVALID |
UNABLE_TO_REACH |
NOT_SUPPORTED |
|---|---|---|---|---|
CONFIRMED |
Supported | Supported | Supported | Not applicable |
PROBABLE |
Supported | Supported | Supported | Not applicable |
RULE_BASED |
Supported | Supported | Not applicable | Not applicable |
NONE |
Not applicable | Not applicable | Not applicable | Supported |
| Validation assurance | Behavior |
|---|---|
CONFIRMED |
The partner provides a direct validation API and treats VALID and INVALID as confident decisions. Temporary connectivity failures can return UNABLE_TO_REACH. |
PROBABLE |
The partner or integration aggregator provides a validation API, but its VALID or INVALID decision may be an assumption. Merit forwards that decision unchanged. Connectivity failures can return UNABLE_TO_REACH. |
RULE_BASED |
Merit applies offline rules supplied by the partner. Results are VALID or INVALID; no partner network call is made. |
NONE |
Pre-transaction member validation is unavailable. The validation endpoint returns NOT_SUPPORTED, and validation occurs when the transaction is posted. |
A VALID result does not guarantee that a later transaction will succeed. Member state may change, and transaction
posting performs its own validation.
Transaction Status
| Status | Description |
|---|---|
| CREATED | This is when the order is created in the system. The API Client will never see this status. |
| QUEUED | This occurs when the client is a prepaid client and the client doesn't have enough funds in their account. |
| IN_PROCESS | For prepaid clients, this is after successful payment. For postpaid clients, this is the first status of the transaction that the API Client sees. |
| COMPLETED | Transaction completed successfully |
| FAILED | Transaction failed |
Status Code
Addition to Transaction Status. Status code gives more context about Status.
| Status Code | Description |
|---|---|
| SUCCESS | Operation complete successfully |
| INSUFFICIENT_FUNDS | Special status code for QUEUED. If client doesn't have enough funds in their account. |
| INVALID_MEMBER | Member used to create transaction does not exists. |
| EXCHANGE_PARTNER_NOT_REACHABLE | Something goes wrong. We cannot reach partner to create transaction |
| UNABLE_TO_PROCESS | Transaction failed completely. Reach out to dev team for more details |
| MAXIMUM_AMOUNT_REACHED | Amount of points used to create the transaction exceeds the limit. |
Error Format
Errors structure
| Field | Description | Type |
|---|---|---|
| code | Error Code | string |
| message | Short description of the error | string |
| details | Optional Object. Details about error | Optional object([string][string]) |
Validation Error Example:
{
"code": "VALIDATION_ERROR",
"message": "Validation error",
"details": {
"field": "body.destination_amount",
"issue": "Input should be a valid integer, got a string"
}
}
Client Error Example:
{
"code": "HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM",
"message": "You have no access to loyalty program",
"details": {}
}
| Error Code | HTTP code | Meaning |
|---|---|---|
| VALIDATION_ERROR | 400 | Bad Request -- Your request is invalid. |
| DUPLICATE_REFERENCE_ID | 400 | Bad Request -- Reference id should be unique. |
| INCORRECT_DESTINATION_AMOUNT | 400 | Bad Request -- Destination amount does not much destination amount after exchange. |
| INCORRECT_BONUS_AMOUNT | 400 | Bad Request -- Bonus amount does not much bonus amount for campaign. |
| CAMPAIGN_NOT_AVAILABLE | 400 | Bad Request -- Campaign expired or does not exists |
| MIN_AMOUNT_LIMIT_NOT_REACHED | 400 | Bad Request -- Destination amount has not reached the minimum amount limit. |
| MAX_AMOUNT_LIMIT_REACHED | 400 | Bad Request -- Destination amount has reached the maximum amount limit. |
| UNAUTHORIZED | 401 | Unauthorized -- Your API key is wrong. |
| ACCESS_DENIED | 403 | Forbidden -- You have no access to resource. General error. |
| HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM | 403 | Forbidden -- You have no access to loyalty program. |
| NOT_FOUND | 404 | Not Found -- The specified resource could not be found. |
| METHOD_NOT_ALLOWED | 405 | Method Not Allowed -- You tried to access a resource with an invalid method. |
| INTERNAL_ERROR | 500 | Internal Server Error -- We had a problem with our server. Try again later. |
Points Exchange API
The Points Exchange API enables customers to exchange points across supported loyalty programs. For pre-transaction member checks and accepted member attributes, see Member Validation and Supported Member Fields.
List Exchange Out Loyalty Programs
GET {host}/api/v1/loyalty-programs/{loyalty_program_id}/exchange-out?limit=10&offset=0
List all the loyalty-programs to which your loyalty program can be exchanged to. The partnership details of the exchange are also returned as part of this response.
Headers
| Key | Value |
|---|---|
| Accept-Language | en |
| Authorization | Bearer <TOKEN> |
Possible error codes
- UNAUTHORIZED (401)
- ACCESS_DENIED (403)
- HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM (403)
- NOT_FOUND (404)
- METHOD_NOT_ALLOWED (405)
- INTERNAL_ERROR (500)
Loyalty Programs JSON structured like this:
[
{
"loyalty_program_id": "TOPSHERPA",
"loyalty_program_name": "TopSherpa",
"currency_id": "TOPSHERPA",
"currency_name": "Top Sherpa Coins",
"logo_url": "logo.com",
"banner_url": "banner.com",
"category": "Airline",
"brand": "TopSherpa",
"origin_to_destination_rate": 5.00000000,
"destination_min_amount": 10,
"destination_max_amount": 1000,
"required_fields": [
{
"id": "id",
"name": "Member ID"
}
],
"validation_assurance": "CONFIRMED",
"description": "",
"terms_and_conditions": "",
"campaign": {
"id": "56377294-572e-4155-b70a-c47867d6cd01",
"name": "Campaign title",
"start_at": 1727267961,
"end_at": 1727267962,
"reward_rule": {
"type": "FIXED_PERCENTAGE",
"rule": {
"amount": 30
}
}
}
},
{
"loyalty_program_id": "EXAMPLE",
"loyalty_program_name": "Example",
"currency_id": "EXAMPLE",
"currency_name": "Example Coin",
"logo_url": "logo.com",
"banner_url": "banner.com",
"category": "Retail",
"brand": "Example",
"origin_to_destination_rate": 0.00816792,
"destination_min_amount": 5,
"destination_max_amount": 500,
"required_fields": [
{
"id": "id",
"name": "Member ID"
},
{
"id": "first_name",
"name": "Member name"
}
],
"validation_assurance": "RULE_BASED",
"description": "",
"terms_and_conditions": "",
"campaign": null
}
]
Path Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
loyalty_program_id |
Yes | String | Loyalty program ID whose exchange-out partners are requested. |
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
limit |
No | Integer | Maximum number of records; defaults to 10. |
offset |
No | Integer | Number of records to skip; defaults to 0. |
Partner Loyalty Program
| Field | Description | Type |
|---|---|---|
| loyalty_program_id | Deprecated. Use currency_id instead. This field will be removed in a future API version. |
string |
| loyalty_program_name | Loyalty program name | string |
| currency_id | Currency slug used for exchange | string |
| currency_name | Currency name | string |
| logo_url | Loyalty program logo | string |
| banner_url | Loyalty program banner | string |
| category | Loyalty program category | string |
| brand | Loyalty program brand | string |
| description | Loyalty program description | string |
| terms_and_conditions | Loyalty program T&C | string |
| origin_to_destination_rate | Conversion rate. How many origin currency you need for 1 destination currency | float |
| destination_max_amount | int | |
| destination_min_amount | int | |
| required_fields | Required fields to create exchange. Supported Member Fields | object |
| validation_assurance | Validation reliability: CONFIRMED, PROBABLE, RULE_BASED, or NONE |
string |
| campaign | Campaign | object |
Campaign
| Field | Description | Type |
|---|---|---|
| id | Campaign id | string |
| start_at | Epoch time when campaign start | int |
| end_at | Epoch time when campaign end | int |
| reward_rule | Reward Rule | object |
Reward Rule
| Field | Description | Type |
|---|---|---|
| type | Reward type. Currently supported only FIXED_PERCENTAGE | string |
| rule | Reward rule. Object stores all the necessary information to properly calculate reward | object |
Calculate Exchange
POST {host}/api/v1/calculate-exchange/
Headers
| Key | Value |
|---|---|
| Authorization | Bearer <TOKEN> |
Request body:
Create Exchange JSON structured like this:
{
"origin_amount": 50,
"origin_currency_id": "MERIT-COIN",
"destination_currency_id": "TOPSHERPA",
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01"
}
| Field | Description | Type | Required |
|---|---|---|---|
| origin_amount | int | True | |
| origin_currency_id | Origin Currency ID | string | True |
| destination_currency_id | Destination Currency ID | string | True |
| campaign_id | Campaign applied to transaction | string | False |
Possible error codes
- VALIDATION_ERROR (400)
- CAMPAIGN_NOT_AVAILABLE (400)
- MIN_AMOUNT_LIMIT_NOT_REACHED (400)
- MAX_AMOUNT_LIMIT_REACHED (400)
- UNAUTHORIZED (401)
- ACCESS_DENIED (403)
- HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM (403)
- METHOD_NOT_ALLOWED (405)
- INTERNAL_ERROR (500)
Response
Create Exchange JSON structured like this:
{
"origin_amount": 50.0,
"destination_amount": 100.0,
"bonus_amount": 30,
"total_amount": 130,
"origin_currency_id": "MERIT-COIN",
"destination_currency_id": "AL-FURSAN",
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01"
}
| Field | Description | Type |
|---|---|---|
| origin_amount | int | |
| destination_amount | int | |
| origin_currency_id | Origin Currency ID | string |
| destination_currency_id | Destination Currency ID | string |
| bonus_amount | Bonus amount | int |
| campaign_id | Campaign applied to transaction | str |
| total_amount | Total amount of received points | int |
Create Exchange
API to add points to the destination loyalty program.
This API operates asynchronously; the API accepts the request and responds immediately after performing a few validations.
The actual addition of points to the destination loyalty program accounts occur in the background.
Clients will be notified of the final status either through Exchange Webhooks or they can query using Get Exchange By reference_id.
POST {host}/api/v1/exchange/
Headers
| Key | Value |
|---|---|
| Authorization | Bearer <TOKEN> |
Request body:
Create Exchange JSON structured like this:
{
"reference_id": "gvdags-abdvas-dabbwhf",
"origin_amount": 50,
"destination_amount": 100,
"origin_currency_id": "MERIT-COIN",
"destination_currency_id": "TOPSHERPA",
"bonus_amount": 30,
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
"destination_member": {
"id": "member1",
"first_name": "Example name"
},
"origin_member": {
"id": "example_id"
}
}
| Field | Description | Type | Required |
|---|---|---|---|
| reference_id | Unique value | string | True |
| origin_amount | int | True | |
| destination_amount | int | True | |
| origin_currency_id | Origin Currency ID | string | True |
| destination_currency_id | Destination Currency ID | string | True |
| destination_member | Member Object. Object consists of Supported Member Fields | object | True |
| origin_member | Member Object. Object consists of data representing origin member | object | True |
| bonus_amount | Bonus amount | int | False |
| campaign_id | Campaign applied to transaction | string | False |
Possible error codes
- VALIDATION_ERROR (400)
- DUPLICATE_REFERENCE_ID (400)
- INCORRECT_DESTINATION_AMOUNT (400)
- INCORRECT_BONUS_AMOUNT (400)
- CAMPAIGN_NOT_AVAILABLE (400)
- MIN_AMOUNT_LIMIT_NOT_REACHED (400)
- MAX_AMOUNT_LIMIT_REACHED (400)
- UNAUTHORIZED (401)
- ACCESS_DENIED (403)
- HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM (403)
- METHOD_NOT_ALLOWED (405)
- INTERNAL_ERROR (500)
Response
Create Exchange JSON structured like this:
{
"reference_id": "gvdags-abdvas-dabbwhf",
"origin_amount": 50.0,
"destination_amount": 100.0,
"status": "IN_PROCESS",
"status_code": "SUCCESS",
"origin_currency_id": "MERIT-COIN",
"destination_currency_id": "AL-FURSAN",
"bonus_amount": 30,
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
"total_amount": 130,
"destination_transaction": {
"reference_id": "328ccb61acd4ae6ce63b4ae34c42ebbe7f2f02d8"
},
"destination_member": {
"id": "77250666"
},
"origin_member": {
"id": "test"
}
}
| Field | Description | Type |
|---|---|---|
| reference_id | Unique value | string |
| origin_amount | int | |
| destination_amount | int | |
| status | Transaction Status | string |
| status_code | Status Code | string |
| origin_currency_id | Origin Currency ID | string |
| destination_currency_id | Destination Currency ID | string |
| destination_member | Member Object. Object consists of Supported Member Fields | object |
| origin_member | Member Object. Object consists of data representing origin member | object |
| bonus_amount | Bonus amount | int |
| campaign_id | Campaign applied to transaction | string |
| total_amount | Total amount of received points | int |
| destination_transaction | Used for internal purposes at Merit. We do not recommend our clients to use the contents of this field. Provides the details relevant to the transaction at the destination loyalty program. Should be accessed only if the status of this transction is COMPLETED |
object |
Get Exchange By reference_id
GET {host}/api/v1/exchanges/{reference_id}
Headers
| Key | Value |
|---|---|
| Authorization | Bearer <TOKEN> |
Path Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
reference_id |
Yes | String | Client reference ID of the exchange transaction. |
Possible error codes
- VALIDATION_ERROR (400)
- UNAUTHORIZED (401)
- ACCESS_DENIED (403)
- NOT_FOUND (404)
- METHOD_NOT_ALLOWED (405)
- INTERNAL_ERROR (500)
Response
Get Exchange JSON structured like this:
{
"reference_id": "gvdags-abdvas-dabbwhf",
"origin_amount": 50.0,
"destination_amount": 100.0,
"status": "COMPLETED",
"status_code": "SUCCESS",
"origin_currency_id": "MERIT-COIN",
"destination_currency_id": "AL-FURSAN",
"bonus_amount": 30,
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
"total_amount": 130,
"destination_transaction": {
"reference_id": "328ccb61acd4ae6ce63b4ae34c42ebbe7f2f02d8"
},
"destination_member": {
"id": "77250666"
},
"origin_member": {
"id": "test"
}
}
| Field | Description | Type |
|---|---|---|
| reference_id | Unique value | string |
| origin_amount | int | |
| destination_amount | int | |
| status | Transaction Status | string |
| status_code | Status Code | string |
| origin_currency_id | Origin Currency ID | string |
| destination_currency_id | Destination Currency ID | string |
| destination_member | Member Object. Object consists of Supported Member Fields | object |
| origin_member | Member Object. Object consists of data representing origin member | object |
| bonus_amount | Bonus amount | int |
| campaign_id | Campaign applied to transaction | str |
| total_amount | Total amount of received points | int |
| destination_transaction | Used for internal purposes at Merit. We do not recommend our clients to use the contents of this field. Provides the details relevant to the transaction at the destination loyalty program. Should be accessed only if the status of this transction is COMPLETED |
object |
Transaction
| Field | Description | Type |
|---|---|---|
| id | Transaction ID | string |
| reference_id | Unique value | string |
| origin_amount | int | |
| destination_amount | int | |
| status | Transaction Status | string |
| status_code | Status Code | string |
| origin_currency_id | Origin Currency ID | string |
| destination_currency_id | Destination Currency ID | string |
| destination_member | Member Object. Object consists of data taken from required_fields in LoyaltyProgram |
object |
| origin_member | Member Object. Object consists of data representing origin member | object |
| bonus_amount | Bonus amount | int |
| campaign_id | Campaign applied to transaction | str |
| total_amount | Total amount of received points | int |
Exchange Webhooks
Integration Guide
Webhooks provide a powerful mechanism to receive real-time notifications about events occurring within your Merit account. By registering a webhook URL, Merit can directly push event data to your application, enabling immediate and automated responses.
Registering a Webhook URL
To begin receiving webhook notifications, provide your webhook URL to the Merit team. Merit will register this URL and
subscribe it to relevant events based on your application needs. For instance, accounts using Merit's Points-Exchange
APIs will have their webhooks subscribed to related transaction events.
Upon registration, a secret key will be shared with you, essential for the secure verification of incoming
notifications.
Webhook Endpoint Requirements
Your endpoint must be a HTTPS POST endpoint listening on port 443.
Webhook Request Details
| Payload | Description |
|---|---|
| Body | Each webhook notification contains an Event Object in JSON format. |
| Headers | Critical headers include X-Merit-Signature, which is used for message verification. |
{
"Content-Type": "application/json",
"X-Merit-Signature": "t=1492774577,v1=ansdoj213e9,v0=6ffbb59b"
}
Handling Webhook requests
Receiving and processing webhook notifications consists of two primary steps:
- Receipt of Message:
- Your endpoint should acknowledge successful reception by returning an HTTP 2xx status code. Failure to do so
prompts
Meritto retry according to its retry policy.
- Verification of Message:
- Verify the X-Merit-Signature header to ensure the authenticity of the received event. A typical signature header is as follows:
import hmac
import hashlib
import base64
import re
def generate_base64_hmac(timestamp, raw_payload, secret_key):
message = f"{timestamp},{raw_payload}"
secret_key_bytes = str(secret_key).encode('utf-8')
message_bytes = message.encode('utf-8')
hmac_obj = hmac.new(secret_key_bytes, message_bytes, hashlib.sha256)
hmac_digest = hmac_obj.digest()
hmac_base64 = base64.b64encode(hmac_digest)
return hmac_base64.decode()
def verify_signature(signature_header, raw_payload, secret_key):
# Extracting timestamp
timestamp_match = re.search(r't=(\d+)', signature_header)
if not timestamp_match:
return False
timestamp = timestamp_match.group(1)
computed_hash = generate_base64_hmac(timestamp, raw_payload, secret_key)
# Extracting all hashes
hashes = re.findall(r'v\d+=([^,]+)', signature_header)
# Verifying each hash
for expected_hash in hashes:
if hmac.compare_digest(computed_hash, expected_hash):
return True
return False
# Example Use Case
signature_header = "t=1734111281,v1=yo7/S+zHFBg6sYWVcNkmb2j3EzIJ2XEQ0TlYWOaFWm0=,v1=T0kP94n+cL6Za34J8Y8nhAxpcdJxDVfXY5WxGo4Y1Ho="
raw_payload = '{"id":"evt_6820f736-5466-4a64-9858-ac8bf514f421","created_at":1686089970,"event_type":"points_exchange.transaction.created","data":{"id":"id_of_the_transaction","reference_id":"21212gvdags-abdvas-dabbwh12111212f","origin_amount":50,"destination_amount":100,"status":"COMPLETED","origin_currency_id":"MERIT-COIN","destination_currency_id":"AL-FURSAN","bonus_amount":30,"campaign_id":"56377294-572e-4155-b70a-c47867d6cd01","total_amount":130,"destination_member":{"id":"77250666"},"origin_member":{"id":"test"}}}'
secret_key = "0y1SVxadCBUY7MiACPEdPCJGD"
verification_result = verify_signature(signature_header, raw_payload, secret_key)
print("Verification successful." if verification_result else "Verification failed.")
- Verification Process:
- Concatenate the timestamp and payload in the format {timestamp},{payload}.
- Using a HMAC library, compute your signature using the secret key, the concatenated message in step 1 and encode the result in base64.
- (Optional): Verify the timestamp to ensure it's within an acceptable range (e.g., ±10 minutes) to prevent replay attacks.
- Perform a constant time comparison between the computed signature and the received signatures to safeguard against timing attacks.
Retry Policy
Merit attempts to deliver a given event to your webhook endpoint for up to 2 days with an exponential back-off. A max of 15 requests will be attempted.
Events
EventObject Example:
{
"id": "evt_1NG8Du2eZvKYlo2CUI79vXWy",
"created_at": 1686089970,
"event_type": "transaction.completed",
"version": "1.0",
"data": {
"id": "e38f42f7-474d-4720-872b-63b23e516314",
"reference_id": "gvdags-abdvas-dabbwhf",
"origin_amount": 50.0,
"destination_amount": 100.0,
"status": "COMPLETED",
"status_code": "SUCCESS",
"origin_currency_id": "MERIT-COIN",
"destination_currency_id": "AL-FURSAN",
"bonus_amount": 30,
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
"total_amount": 130,
"destination_member": {
"id": "77250666"
},
"origin_member": {
"id": "test"
}
}
}
Event object
| name | description |
|---|---|
| id | Unique identifier of the event |
| created_at | Unix epoch of the time at which this event is generated |
| event_type | The type of the event. The entire list of event types can be found here EventTypes |
| version | The version of the resource object which is available in the data field. This will be of no significance unless there is a version change which impacts the data structure. |
| data | Object containing the data associated with the event. The structure of the object depends on the event_type. The details of the structure can be found in Transaction |
Event Types
This is a list of all the types of events we currently send. We may add more at any time, so in developing and maintaining your code, you should not assume that only these types exist.
| name | description |
|---|---|
| points_exchange.transaction.completed | Occurs when a points-exchange transaction is completed. |
| points_exchange.transaction.failed | Occurs when a points-exchange transaction is failed |
EARN API
The EARN API enables partners to award points to customers for eligible purchases across supported loyalty programs.
For EARN authentication, see Authorization (Client Credentials Flow). For pre-transaction member checks and accepted member attributes, see Member Validation and Supported Member Fields.
List EARN partners
GET {host}/api/v1/loyalty-programs/{currency_id}/earn-partners?limit=10&offset=0
This API lists all partner loyalty programs into whose user accounts a Client can add points, based on spending made on the Client's platform, including web, app, and POS channels.
The response follows the same structure as List Exchange Out Loyalty Programs.
Headers
| Key | Value |
|---|---|
Accept-Language |
en |
Authorization |
Bearer <TOKEN> |
Path Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
currency_id |
Yes | String | The currency ID of a loyalty program that belongs to the Client. If the Client does not have its own loyalty program, Merit creates one because it is required by the system. The Client can retrieve this ID using List my loyalty programs. |
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
limit |
No | Integer | Maximum number of records; defaults to 10 and cannot exceed 100. |
offset |
No | Integer | Number of records to skip; defaults to 0. |
RESPONSE - 200 OK
[
{
"loyalty_program_name": "wala-one",
"currency_id": "WALA_ONE",
"currency_name": "wala-one points",
"logo_url": "https://cdn.example.com/wala-one/logo.svg",
"banner_url": "https://cdn.example.com/wala-one/banner.jpg",
"category": "Retail",
"brand": "wala-one",
"description": "Earn wala-one points on eligible purchases.",
"terms_and_conditions": "Partner terms apply.",
"required_fields": [
{
"id": "mobile_number",
"name": "Mobile number"
},
{
"id": "id",
"name": "Membership ID"
}
],
"validation_assurance": "CONFIRMED",
"partnership_details": {
"partnership_type": "FIAT_TO_POINTS",
"origin_fiat_currency": "SAR",
"origin_currency_id": "MY_LOYALTY_PROGRAM",
"origin_to_destination_rate": "0.600000",
"origin_min_amount": "10.00",
"origin_max_amount": "10000.00",
"destination_min_amount": 6,
"destination_max_amount": 5000,
"reversal_supported": true,
"reversal_window_days": 14
},
"campaign": null
}
]
EARN partner loyalty program
| Field | Description | Type |
|---|---|---|
loyalty_program_name |
Localized loyalty program display name | string |
currency_id |
The currency ID of the loyalty program | string |
currency_name |
Localized destination points currency name | string |
logo_url |
Loyalty program logo URL | string |
banner_url |
Loyalty program banner URL | string |
category |
Loyalty program business category | string |
brand |
Loyalty program brand | string |
description |
Localized EARN description | string |
terms_and_conditions |
Localized EARN terms and conditions | string |
required_fields |
Required member fields. See Supported Member Fields. | object |
validation_assurance |
Validation reliability. See Validation Assurance. | string |
partnership_details |
EARN partnership rate and transaction limits for this loyalty program | object |
partnership_details.partnership_type |
Always FIAT_TO_POINTS for EARN partners |
string |
partnership_details.origin_fiat_currency |
ISO 4217 currency in which the client transaction is priced | string |
partnership_details.origin_currency_id |
Origin loyalty-program currency ID from which this EARN partnership is available | string |
partnership_details.origin_to_destination_rate |
Destination points earned for one unit of origin_fiat_currency; returned as an exact decimal string |
decimal string |
partnership_details.origin_min_amount |
Minimum eligible transaction value in the origin fiat currency | decimal string |
partnership_details.origin_max_amount |
Maximum eligible transaction value in the origin fiat currency | decimal string |
partnership_details.destination_min_amount |
Minimum points that may be earned in one transaction | integer |
partnership_details.destination_max_amount |
Maximum points that may be earned in one transaction | integer |
partnership_details.reversal_supported |
Whether this EARN partnership accepts transaction reversals | boolean |
partnership_details.reversal_window_days |
Number of days within which a reversal is allowed; applicable when reversal is supported | integer |
campaign |
Active Campaign configuration, or null when no campaign applies |
object |
Calculate EARN
POST {host}/api/v1/calculate-earn/
Calculates the number of points that will be credited to the destination loyalty program's account for a given fiat spend.
Headers
| Key | Value |
|---|---|
Authorization |
Bearer <TOKEN> |
Request body
{
"origin_amount": "1000.00",
"origin_fiat_currency": "SAR",
"origin_currency_id": "MY_LOYALTY_PROGRAM",
"destination_currency_id": "WALA_ONE",
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01"
}
| Field | Description | Type | Required |
|---|---|---|---|
origin_amount |
Eligible transaction value in the origin currency | decimal string | true |
origin_fiat_currency |
ISO 4217 transaction currency returned by EARN partner discovery | string | true |
origin_currency_id |
Origin loyalty-program currency ID used to select the EARN partnership | string | true |
destination_currency_id |
Destination points currency ID returned by EARN partner discovery | string | true |
campaign_id |
Active campaign applied to the calculation | string | false |
Possible error codes
- VALIDATION_ERROR (400)
- CAMPAIGN_NOT_AVAILABLE (400)
- MIN_AMOUNT_LIMIT_NOT_REACHED (400)
- MAX_AMOUNT_LIMIT_REACHED (400)
- UNAUTHORIZED (401)
- ACCESS_DENIED (403)
- HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM (403)
- NOT_FOUND (404)
- METHOD_NOT_ALLOWED (405)
- INTERNAL_ERROR (500)
Response
{
"origin_amount": "1000.00",
"destination_amount": 600,
"bonus_amount": 0,
"total_amount": 600,
"origin_fiat_currency": "SAR",
"origin_currency_id": "MY_LOYALTY_PROGRAM",
"destination_currency_id": "WALA_ONE",
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01"
}
| Field | Description | Type |
|---|---|---|
origin_amount |
Transaction value used for the calculation | decimal string |
destination_amount |
Base destination points before campaign bonus | integer |
bonus_amount |
Additional points earned from the campaign | integer |
total_amount |
Total points: destination_amount + bonus_amount |
integer |
origin_fiat_currency |
Origin ISO 4217 transaction currency | string |
origin_currency_id |
Origin loyalty-program currency ID used by the EARN partnership | string |
destination_currency_id |
Destination points currency ID receiving the points | string |
campaign_id |
Applied campaign ID, or null |
string |
The rate, campaign, FX conversion, limits, and rounding are evaluated again when the EARN transaction is posted.
Post an EARN transaction
POST {host}/api/v1/earn/
Adds points to the destination loyalty program. The endpoint follows Create Exchange, with a fiat origin and EARN-specific channel and reporting fields.
This API operates asynchronously. It accepts the request and responds after validating the partner, member fields, amounts, campaign, and client funding. Merit then submits the accrual to the partner. Clients receive the final status by webhook or through the EARN status endpoint.
Headers
| Key | Value |
|---|---|
Authorization |
Bearer <TOKEN> |
Request body
{
"reference_id": "ORDER-10492",
"origin_fiat_amount": "1000.00",
"destination_amount": 600,
"origin_fiat_currency": "SAR",
"origin_currency_id": "MY_LOYALTY_PROGRAM",
"destination_currency_id": "WALA_ONE",
"bonus_amount": 0,
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
"destination_member": {
"mobile_number": "+966555555555"
},
"category": "ELECTRONICS",
"branch_id": "RIYADH-01",
"note": "Fulfilled order"
}
| Field | Description | Type | Required |
|---|---|---|---|
reference_id |
Unique value | string | true |
origin_fiat_amount |
Eligible transaction value in the origin fiat currency | decimal string | true |
destination_amount |
integrer | true | |
origin_fiat_currency |
ISO 4217 transaction currency | string | true |
origin_currency_id |
Origin Currency ID | string | true |
destination_currency_id |
Destination Currency ID | string | true |
destination_member |
Member Object. Object consists of Supported Member Fields | object | true |
bonus_amount |
Bonus amount | integer | false |
campaign_id |
Campaign applied to the transaction | string | false |
category |
Client transaction category. Maximum 100 characters; allowed characters are letters, numbers, _, and -. |
string | false |
branch_id |
Client branch identifier. Maximum 100 characters; allowed characters are letters, numbers, _, and -. |
string | false |
note |
Optional PII-free transaction note; maximum 500 characters | string | false |
Possible error codes
- VALIDATION_ERROR (400)
- DUPLICATE_REFERENCE_ID (400)
- INCORRECT_DESTINATION_AMOUNT (400)
- INCORRECT_BONUS_AMOUNT (400)
- CAMPAIGN_NOT_AVAILABLE (400)
- MIN_AMOUNT_LIMIT_NOT_REACHED (400)
- MAX_AMOUNT_LIMIT_REACHED (400)
- INCORRECT_MEMBER (400)
- UNAUTHORIZED (401)
- ACCESS_DENIED (403)
- HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM (403)
- METHOD_NOT_ALLOWED (405)
- INTERNAL_ERROR (500)
Response
{
"id": "etx_01K0X9V65AB8KE2B7JTZQ5QG7V",
"transaction_operation": "CREDIT",
"parent_transaction_id": null,
"reference_id": "ORDER-10492",
"origin_fiat_amount": "1000.00",
"destination_amount": 600,
"status": "IN_PROCESS",
"status_code": "SUCCESS",
"origin_fiat_currency": "SAR",
"origin_currency_id": "MY_LOYALTY_PROGRAM",
"destination_currency_id": "WALA_ONE",
"bonus_amount": 0,
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
"total_amount": 600,
"destination_transaction": {
"reference_id": null
},
"destination_member": {
"mobile_number": "+966******555"
},
"category": "ELECTRONICS",
"branch_id": "RIYADH-01",
"note": "Fulfilled order"
}
| Field | Description | Type |
|---|---|---|
id |
Merit transaction ID used for reversals and internal traceability | string |
transaction_operation |
EARN operation represented by the transaction: CREDIT or REVERSAL |
string |
parent_transaction_id |
Original EARN transaction ID for a reversal; null for a credit transaction |
string or null |
reference_id |
Unique value | string |
origin_fiat_amount |
Original fiat transaction value | decimal string |
destination_amount |
integer | |
status |
Transaction Status | string |
status_code |
Status Code | string |
origin_fiat_currency |
Origin ISO 4217 transaction currency | string |
origin_currency_id |
Origin Currency ID | string |
destination_currency_id |
Destination Currency ID | string |
bonus_amount |
Bonus amount | integer |
campaign_id |
Campaign applied to transaction | string |
total_amount |
Total amount of received points | integer |
destination_transaction |
Used for internal purposes at Merit. We do not recommend our clients to use the contents of this field. Provides the details relevant to the transaction at the destination loyalty program. Should be accessed only if the status of this transction is COMPLETED |
object |
destination_member |
Member Object. Object consists of Supported Member Fields | object |
category |
Client transaction category | string |
branch_id |
Client branch identifier. | string |
note |
string |
Clients must integrate reversals even if they post at fulfillment. For e-commerce, Merit recommends calculating at checkout and posting at fulfillment. For POS, Merit recommends posting when the sale completes.
Get an EARN transaction
GET {host}/api/v1/earn/transactions/{transaction_id}
Returns a credit or reversal owned by the authenticated client. Polling is a fallback for clients that cannot consume webhooks.
Headers
| Header | Required | Value |
|---|---|---|
Authorization |
Yes | Bearer <TOKEN> |
Path Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
transaction_id |
Yes | String | Merit transaction ID returned in the id field of the Post EARN transaction response. |
Possible error codes
- VALIDATION_ERROR (400)
- UNAUTHORIZED (401)
- ACCESS_DENIED (403)
- NOT_FOUND (404)
- METHOD_NOT_ALLOWED (405)
- INTERNAL_ERROR (500)
RESPONSE - 200 OK
{
"id": "etx_01K0X9V65AB8KE2B7JTZQ5QG7V",
"transaction_operation": "CREDIT",
"parent_transaction_id": null,
"reference_id": "ORDER-10492",
"origin_fiat_amount": "1000.00",
"destination_amount": 600,
"status": "COMPLETED",
"status_code": "SUCCESS",
"origin_fiat_currency": "SAR",
"origin_currency_id": "MY_LOYALTY_PROGRAM",
"destination_currency_id": "WALA_ONE",
"bonus_amount": 0,
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
"total_amount": 600,
"destination_transaction": {
"reference_id": "WALA-849201"
},
"destination_member": {
"mobile_number": "+966******555"
},
"category": "ELECTRONICS",
"branch_id": "RIYADH-01",
"note": "Fulfilled order"
}
The response uses the same fields as the response of
Post an EARN transaction. For a completed
transaction, destination_transaction.reference_id contains the partner transaction reference.
Reverse an EARN transaction
POST {host}/api/v1/earn/transactions/{transaction_id}/reversals
Reverses points from a completed EARN transaction. Partial reversals are not supported at the moment. To keep the API
flexible, we accept an origin_amount parameter so it can be extended to partial reversals in the future. For now, pass
the full amount of the parent transaction you want to reverse. A pending EARN credit must reach a terminal state before
it can be reversed. The endpoint is available only when partner discovery returns reversal_supported: true, and the
request must be submitted within the partnership's reversal_window_days.
Headers
| Key | Value |
|---|---|
Authorization |
Bearer <TOKEN> |
Path Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
transaction_id |
Yes | String | Merit ID of the original completed EARN credit to reverse. |
Request body
{
"reference_id": "REFUND-10492-1",
"origin_amount": "250.00",
"origin_fiat_currency": "SAR",
"note": "One item returned"
}
| Field | Description | Type | Required |
|---|---|---|---|
reference_id |
Client-generated unique reversal reference. This should be a new reference_id different from the reference_id of the parent transaction. |
string | true |
origin_amount |
Refunded amount in the original transaction currency | decimal string | true |
origin_fiat_currency |
ISO 4217 transaction currency returned by EARN partner discovery | string | true |
note |
Optional PII-free explanation; maximum 500 characters | string | false |
origin_amount must be positive and cannot exceed the original transaction's remaining reversible amount. Merit
computes the points to reverse from the original transaction pricing.
Possible error codes
- VALIDATION_ERROR (400)
- DUPLICATE_REFERENCE_ID (400)
- TRANSACTION_NOT_REVERSIBLE (400)
- REVERSAL_AMOUNT_EXCEEDED (400)
- REVERSAL_WINDOW_EXPIRED (400)
- UNAUTHORIZED (401)
- ACCESS_DENIED (403)
- NOT_FOUND (404)
- METHOD_NOT_ALLOWED (405)
- INTERNAL_ERROR (500)
Response
{
"id": "erv_01K0XB1F1GCXQTX42G2Z19F09Y",
"transaction_operation": "REVERSAL",
"parent_transaction_id": "etx_01K0X9V65AB8KE2B7JTZQ5QG7V",
"reference_id": "REFUND-10492-1",
"origin_amount": "250.00",
"destination_amount": 150,
"status": "IN_PROCESS",
"status_code": "SUCCESS",
"origin_fiat_currency": "SAR",
"origin_currency_id": "MY_LOYALTY_PROGRAM",
"destination_currency_id": "WALA_ONE",
"bonus_amount": 0,
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
"total_amount": 150,
"destination_transaction": {
"reference_id": null
},
"destination_member": {
"mobile_number": "+966******555"
},
"category": "ELECTRONICS",
"branch_id": "RIYADH-01",
"note": "Fulfilled order"
}
| Field | Description | Type |
|---|---|---|
id |
Merit transaction ID for the reversal | string |
transaction_operation |
Transaction operation; always REVERSAL for this endpoint |
string |
parent_transaction_id |
Merit transaction ID of the original EARN credit being reversed | string |
reference_id |
Client-generated unique reference ID for the reversal | string |
origin_amount |
Full fiat amount of the parent transaction submitted for reversal | decimal string |
destination_amount |
Number of base points to reverse, represented as a string with two decimal places | decimal string |
status |
Transaction Status | string |
status_code |
Status Code | string |
origin_fiat_currency |
ISO 4217 currency of the parent transaction's fiat amount | string |
origin_currency_id |
Origin Currency ID from the parent transaction | string |
destination_currency_id |
Destination Currency ID from the parent transaction | integer |
bonus_amount |
Bonus points to reverse | integer |
campaign_id |
Campaign applied to the parent transaction, or null |
string |
total_amount |
Total points to reverse, including bonus points | integer |
destination_transaction |
Partner reversal transaction details; reference_id may be null until completion |
object |
destination_member |
Member Object. Object consists of Supported Member Fields | object |
category |
Client transaction category | string |
branch_id |
Client branch identifier. | string |
note |
string |
Reversal points are calculated using the original transaction's ratio, promotion multiplier, FX snapshot, and rounding rule. Current pricing is never used.
Reversals outside the configured partner window return 400 REVERSAL_WINDOW_EXPIRED and are routed for manual support.
HTTP errors use the existing {code, message, details} envelope:
| Error code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR |
400 | Request shape or field value is invalid |
DUPLICATE_REFERENCE_ID |
400 | The client reference_id has already been used |
INCORRECT_DESTINATION_AMOUNT |
400 | Destination amount does not match Merit's calculation |
INCORRECT_BONUS_AMOUNT |
400 | Bonus amount does not match the selected campaign |
CAMPAIGN_NOT_AVAILABLE |
400 | Campaign does not exist, is inactive, or is unavailable |
MIN_AMOUNT_LIMIT_NOT_REACHED |
400 | Transaction amount is below the configured minimum |
MAX_AMOUNT_LIMIT_REACHED |
400 | Transaction amount exceeds the configured maximum |
INCORRECT_MEMBER |
400 | Required member data is missing or incorrect |
TRANSACTION_NOT_REVERSIBLE |
400 | Transaction state or partnership configuration prevents reversal |
REVERSAL_AMOUNT_EXCEEDED |
400 | Requested reversal exceeds the remaining reversible amount |
REVERSAL_WINDOW_EXPIRED |
400 | Partner reversal window elapsed |
UNAUTHORIZED |
401 | Bearer token is missing or invalid |
ACCESS_DENIED |
403 | Client does not have access to the requested resource |
HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM |
403 | Client does not have access to the requested loyalty program |
NOT_FOUND |
404 | Loyalty program or transaction was not found |
METHOD_NOT_ALLOWED |
405 | HTTP method is not supported for the endpoint |
INTERNAL_ERROR |
500 | Unexpected Merit failure |
EARN webhooks
Credits and refunds use the same webhook event types and transaction body. Inspect transaction_operation to identify
the operation: CREDIT means points were earned and REVERSAL means points were refunded. Credit events have
parent_transaction_id: null; refund events contain parent_transaction_id with the original credit transaction ID.
Events may be delivered more than once or arrive out of order. Deduplicate by event id, acknowledge with HTTP 2xx,
and apply an event only when it represents a newer resource state. Retrieve the transaction when local state is
uncertain. The event idempotency_key identifies webhook delivery; it is unrelated to the request Idempotency-Key.
EARN event types
| Event type | Description |
|---|---|
earn.transaction.completed |
An EARN credit or refund completed successfully |
earn.transaction.failed |
An EARN credit or refund failed |
EARN EVENT EXAMPLE
{
"id": "evt_01K0XC93A1MXCKF9QN6T5Z0G7Z",
"created_at": 1784727063,
"event_type": "earn.transaction.completed",
"version": "1.0",
"idempotency_key": "delivery_01K0XC93B8R4P9RZRJ2Q3V1JRA",
"data": {
"id": "etx_01K0X9V65AB8KE2B7JTZQ5QG7V",
"transaction_operation": "CREDIT",
"parent_transaction_id": null,
"reference_id": "ORDER-10492",
"origin_fiat_amount": "1000.00",
"destination_amount": 600,
"status": "COMPLETED",
"status_code": "SUCCESS",
"origin_fiat_currency": "SAR",
"origin_currency_id": "MY_LOYALTY_PROGRAM",
"destination_currency_id": "WALA_ONE",
"bonus_amount": 0,
"campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
"total_amount": 600,
"destination_transaction": {
"reference_id": "WALA-849201"
},
"destination_member": {
"mobile_number": "+966******555"
},
"category": "ELECTRONICS",
"branch_id": "RIYADH-01",
"note": "Fulfilled order"
}
}