Policy Status Notification

Policy callback API is provided by the partner and is the way Qoala shares the latest status of the transaction. Through this callback API, Qoala will share policy information based on each transaction, such as policy_number, policy_status, and a link to the e-certificate. Qoala's system supports several methods of authorization: API Key, HMAC_SHA256, MD5, Bearer (JWT or static token), and Basic Auth. Qoala can support a new method of authorization if needed.

Callback URL

Each partner can configure a callback URL for policy creation webhook.

POST {partner-url-path}
partner-url-path = URL from partner to receive webhook notification

The configuration can be done through at the time of onboarding.

Authentication

Qoala authenticates every policy status notification using credentials and header names agreed during onboarding.

The examples below show the methods currently supported. Header names, message format, and encoding are partner-specific and will be confirmed during onboarding.

API Key

The shared API key is sent in a request header. Partner can verify the request by matching this value with the key exchanged during onboarding.

x-api-key: 6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39
Content-Type: application/json

HMAC_SHA256

Qoala signs the request with HMAC-SHA256 using a shared secret. A typical configuration generates a timestamp, serializes the mapped JSON body, then signs:

message = {apikey}|{timestamp}|{raw_json_body}
signature = Base64(HMAC-SHA256(secret, message))

The signature is computed after the request body is built, so it covers the exact JSON payload that is sent.

Example mapping

PlaceholderResolved value
apikeyINS_QL_12345678 (sent as X-API-Key)
secretShared HMAC secret (used only to sign, never sent)
timestampCurrent UTC time in ISO 8601, for example 2026-07-16T10:00:00Z
body_stringRaw JSON body after field mapping
signatureBase64 HMAC-SHA256 digest

Body fields in this example are mapped as follows: status is sent as actual_status. Other fields keep the same name (policy_number, partner_transaction_number, quotation_number, product_code, start_protection_at, end_protection_at).

Example request

POST /api/notify/your/endpoint/callback
Content-Type: application/json
X-API-Key: INS_QL_12345678
X-Timestamp: 2026-07-16T10:00:00Z
X-Signature: YNlgRaQK5bh6aRpcMmPq33xBhq0uREkhQmE1yKDLF8I=
{
  "actual_status": "POLICY_ACTIVE",
  "end_protection_at": "2028-07-15T16:59:59+07:00",
  "partner_transaction_number": "TX-20260716-001",
  "policy_number": "G-ADLD-PARTNER-003-20260716-INQ4M",
  "product_code": "G-ADLD-PARTNER-003",
  "quotation_number": "QS-GADGET-20260716-ABC12",
  "start_protection_at": "2026-07-15T17:00:00+07:00"
}

The sample X-Signature above is produced with secret partner_hmac_secret over this concatenated message:

INS_QL_00000000|2026-07-16T10:00:00Z|{"actual_status":"POLICY_ACTIVE","end_protection_at":"2028-07-15T16:59:59+07:00","partner_transaction_number":"TX-20260716-001","policy_number":"G-ADLD-PARTNER-003-20260716-INQ4M","product_code":"G-ADLD-PARTNER-003","quotation_number":"QS-GADGET-20260716-ABC12","start_protection_at":"2026-07-15T17:00:00+07:00"}

How to verify

  1. Read X-API-Key, X-Timestamp, and the raw request body (do not re-serialize the JSON).
  2. Build message = X-API-Key + "|" + X-Timestamp + "|" + raw_body.
  3. Compute Base64(HMAC-SHA256(shared_secret, message)).
  4. Compare the result with X-Signature using a constant-time comparison.

MD5

Qoala can send an MD5 hex digest of a concatenated message. The digest may be placed in a header or in a body field (commonly Sign), depending on the partner contract. The digest is always a 32-character lowercase hex string.

signature = hex(MD5(format(params...)))

Example

If the agreed message is {secret}{start_date}{partner_transaction_number}:

secret = partner_md5_secret
message = partner_md5_secret2026-07-16TX-20260716-001
Sign    = f6656a796f452b4630ce9362ccafa59c
{
  "partner_transaction_number": "TX-20260716-001",
  "start_protection_at": "2026-07-16",
  "status": "POLICY_ACTIVE",
  "Sign": "f6656a796f452b4630ce9362ccafa59c"
}

Verify by rebuilding the same concatenated string from the received body (and shared secret) and comparing the MD5 hex digest.

Bearer

Qoala sends an Authorization: Bearer <token> header. The token can be:

  • A static token exchanged during onboarding
  • A JWT signed by Qoala (HS256, HS384, or HS512)
  • A token fetched from the partner's auth endpoint (OAuth / REST) immediately before the callback

Static token

Authorization: Bearer partner_static_token
Content-Type: application/json

JWT

Qoala signs a JWT with the shared secret. Default claims:

ClaimValue
issQOALA
expToken expiry (default 5 minutes from issuance, configurable)

Additional custom claims can be included when required.

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

Verify the JWT using the shared secret, signing method, and expected issuer QOALA.

Token from partner auth endpoint

If the partner requires Qoala to obtain a token first, Qoala calls the partner auth URL, reads the token from the response (for example access_token), prefixes it with Bearer , and sends it on the policy callback:

Authorization: Bearer {access_token_from_auth_response}
Content-Type: application/json

Basic Auth

Qoala sends HTTP Basic Authentication. Username and password are concatenated, Base64-encoded, and prefixed with Basic .

credential = Base64("USERNAME:{password}")
Authorization: Basic {credential}

Example

username:password = qoala:partner_password
Authorization: Basic cW9hbGE6cGFydG5lcl9wYXNzd29yZA==
Content-Type: application/json

Webhook Request

Each time a status is updated then a request with the following details will be made against the specified Callback URL [partner-url-path]

Headers

The following are the details of the request headers of the webhook. Authentication headers depend on the method configured during onboarding. See Authentication.

HeaderExpected Value
Content-Typeapplication/json
x-api-key / X-API-KeyAPI key, when API Key or HMAC_SHA256 is used
X-SignatureHMAC-SHA256 signature, when HMAC_SHA256 is used
X-TimestampRequest timestamp, when HMAC_SHA256 is used
AuthorizationBearer <token> or Basic <credential>, when Bearer or Basic Auth is used

Body

The following are the details of the request body of the webhook.

Object FieldsTypeRequiredDescription
policy_numberstringYesPolicy number generated by Qoala
partner_transaction_numberstringNoThe Transaction ID serves as a unique identifier for each transaction initiated through the API. Partners are responsible for generating this ID and ensuring its uniqueness across their transactions.
transaction_numberstringNoTransaction number generated by Qoala or the partner, depending on the integration flow
quotation_numberstringNoUnique quotation number generated by Qoala system
product_codestringNoProduct code defined by Qoala
insurance_numberstringNoInsurance policy number assigned by the insurer, if available
statusstringYesLast policy status. Enum: POLICY_ACTIVE, POLICY_ISSUING, POLICY_CANCELLED
start_protection_atstringNoStart date and time of the policy protection period (ISO 8601)
end_protection_atstringNoEnd date and time of the policy protection period (ISO 8601)
purchased_atstringNoTimestamp when the policy was purchased (ISO 8601)
activated_atstringNoTimestamp when the policy was activated (ISO 8601)
currency_codestringNoCurrency code of the policy
payer_typestringNoEntity that pays for the policy
calculationobjectNoPremium and sum insured calculation. See calculation table below
policy_holderobjectNoPolicy holder information. See policy_holder table below
insuredsarrayNoList of insured parties. Structure differs by type — see insureds tables below
productobjectNoProduct details. See product table below
documentsarrayNoPolicy documents such as e-certificate and policy wording. See documents table below
policiesarrayNoPolicy list used in simplified async flows (non-Gadget). Each item contains number, status, start_protection_at, end_protection_at, premium_amount, and documents

calculation

FieldTypeRequiredDescription
gwpnumberNoGross written premium
total_sum_insurednumberNoTotal sum insured amount

policy_holder

FieldTypeRequiredDescription
full_namestringNoFull name of the policy holder
emailstringNoEmail address
phone_numberstringNoPhone number in international format

insureds

Each item in the insureds array represents an insured party. The detail object structure depends on type.

FieldTypeRequiredDescription
uuidstringNoUnique identifier for the insured record
typestringNoInsured type. See supported values below
detailobjectNoInsured details. Structure varies by type — see tables below
documentsarrayNoSupporting documents for the insured

Supported type values: USER, AGENT, GADGET, TRAVEL, FLIGHT, VEHICLE, GOODS, PET, LOAN, SHIPPING, PACKAGE, EXPERIENCE, HOSPITALITY, USER_LENDER, USER_BORROWER, BENEFICIARY, OWNER

insureds[].detail (type = USER / USER_LENDER / USER_BORROWER / BENEFICIARY / OWNER)

FieldType
identity_typestring
identity_numberstring
full_namestring
emailstring
phone_numberstring
dial_codestring
birth_datestring
genderstring
relationshipstring
addressstring
occupationstring
citystring
countrystring
place_of_birthstring
agenumber
sum_insurednumber
additional_infoobject

insureds[].detail (type = AGENT)

FieldType
identity_typestring
identity_numberstring
full_namestring
emailstring
phone_numberstring
store_locationstring
store_namestring

insureds[].detail (type = GADGET)

FieldType
identity_typestring
identity_numberstring
sum_insurednumber
manufacturerstring
brandstring
seriesstring
modelstring
device_categorystring
pricenumber
covered_type_unitstring
covered_type_maxnumber
activation_numberstring
external_urlstring
upload_activation_atstring

insureds[].detail (type = VEHICLE)

FieldType
plate_codestring
plate_numberstring
categorystring
manufacturerstring
brandstring
seriesstring
yearnumber
pricenumber
accessories_pricenumber
accessories_notestring
usage_typestring
conditionstring
protectionstring
chassis_numberstring
engine_numberstring
registration_numberstring

insureds[].detail (type = TRAVEL / FLIGHT)

FieldType
identity_typestring
identity_numberstring
booking_timestring
namestring
numberstring
departurestring
departure_timestring
departure_time_offsetnumber
arrivalstring
arrival_timestring
arrival_time_offsetnumber
classstring
sub_classstring
city_originstring
city_destinationstring
country_originstring
country_destinationstring
timezone_originstring
timezone_destinationstring
total_price_ticketnumber
sub_total_price_ticketnumber
trip_typestring
trip_ordernumber
number_of_roomnumber
total_coverednumber
additional_infoobject

insureds[].detail (type = GOODS)

FieldType
identity_typestring
identity_numberstring
sum_insurednumber
categorystring
brandstring
modelstring
pricenumber
addressstring
additional_infoobject

insureds[].detail (type = PET)

FieldType
identity_typestring
identity_numberstring
full_namestring
birth_datestring
genderstring
breedstring
pure_breedboolean
colorstring
categorystring
usage_typestring
vaccinationarray
conditionstring
pre_existing_conditionstring
number_of_pets_in_householdnumber
number_of_pets_with_same_insurancenumber

insureds[].detail (type = LOAN)

FieldType
identity_typestring
identity_numberstring
loan_amountnumber
sum_insurednumber
loan_total_principalnumber
loan_total_principal_percentagenumber
tenure_valuenumber
tenure_periodstring
interest_ratenumber
interest_periodstring
additional_infoobject

insureds[].detail (type = SENDER / RECIPIENT)

FieldType
identity_typestring
identity_numberstring
full_namestring
emailstring
phone_numberstring
birth_datestring
genderstring
addressstring
occupationstring
additional_dataobject

additional_data fields: city, country, zipcode, subject

insureds[].detail (type = SHIPPING)

FieldType
carrierstring
identity_typestring
identity_numberstring
servicestring
total_package_pricenumber
costnumber
estimated_delivery_timestring
input_package_atstring
estimated_delivery_time_offsetnumber
input_package_at_offsetnumber

insureds[].detail (type = PACKAGE)

FieldType
namestring
identity_typestring
identity_numberstring
pricenumber
sum_insurednumber

insureds[].documents[]

FieldType
filenamestring
typestring
urlstring

product

FieldTypeRequiredDescription
countrystringNoProduct country
parent_categorystringNoParent product category
product_categorystringNoProduct sub-category
product_summarystringNoShort product summary
codestringNoProduct code
namestringNoProduct name
languagestringNoProduct language
category_namestringNoCategory display name
insurance_namestringNoInsurance provider name
partner_namestringNoPartner name
product_codestringNoProduct code (duplicate of code in some payloads)
product_namestringNoProduct display name
product_descriptionstringNoHTML product description
product_termsstringNoHTML product terms
product_wordingstringNoURL to product wording document
claim_descriptionstringNoClaim description text
timezonestringNoProduct timezone
termsstringNoAdditional terms
riplay_documentstringNoRIPLAY document reference
benefitsarrayNoList of product benefits. See product.benefits[] table below

product.benefits[]

FieldTypeDescription
codestringBenefit code
namestringBenefit name
descriptionstringBenefit description
term.inclusionarrayList of inclusion terms
term.exclusionarrayList of exclusion terms

documents

FieldTypeRequiredDescription
typestringNoDocument type. Examples: POLICY_CERTIFICATE, POLICY_WORDING
filenamestringNoDocument filename
aliasstringNoHuman-readable document alias
urlstringNoURL to download the document

Example Request Body

Purpose (simplified async flow)

{
  "partner_transaction_number": "TX-20260716-001",
  "transaction_number": "QS-GADGET-20260716-ABC12",
  "product_code": "G-ADLD-PARTNER-003",
  "policies": [
    {
      "number": "G-ADLD-PARTNER-003-20260716-INQ4M",
      "status": "POLICY_ACTIVE",
      "start_protection_at": "2026-07-15T17:00:00+07:00",
      "end_protection_at": "2028-07-15T16:59:59+07:00",
      "premium_amount": 3270000,
      "documents": [
        {
          "type": "POLICY_CERTIFICATE",
          "filename": "G-ADLD-PARTNER-003-20260716-INQ4M.pdf",
          "alias": "policy_certificate.pdf",
          "url": "https://api.uat.qoala.app/api/v2/policies/documents/policy_certificate.pdf"
        },
        {
          "type": "POLICY_WORDING",
          "filename": "Qoala-Gadget-Protection-Terms-Conditions.pdf",
          "alias": "policy_wording.pdf",
          "url": "https://api.uat.qoala.app/api/v2/policies/documents/policy_wording.pdf"
        }
      ]
    }
  ]
}

Webhook Response

HTTP response status code from partner will be tracked and

  • HTTP status code 200 will be acknowledged by Qoala as notification successfully received. No retry will be done.
  • Other HTTP status code will be marked as failed and will be retried. Request will be retried up to 8 times with exponential backoff interval.