Webhook events
milestone.reported
Sent once for each new milestone report, rewarded or not.
Example payload
This illustrative payload follows the current event validator. Amounts use minor units.
json
{
"id": "00000000-0000-4000-8000-000000000001",
"companyId": "company-example",
"type": "milestone.reported",
"data": {
"partners": [
{
"partnerId": "partner-example",
"partnerExternalId": "merchant-partner-001"
}
],
"eventId": "payment-example",
"customerId": "customer-example",
"milestone": "first_booking",
"status": "recorded",
"programIds": [
"program-example"
],
"rewardIds": [
"reward-example"
],
"occurredAt": "2026-10-11T12:00:00.000Z",
"decisions": [
{
"programId": "program-example",
"programName": "programName-example",
"outcome": "rewarded",
"reason": "Merchant review",
"amountCents": 500
}
]
},
"createdAt": "2026-10-11T12:00:00.000Z"
}Payload fields
| Field | Type | Required | Details |
|---|---|---|---|
partners | array | Yes | |
partnerId | string | No | |
partnerExternalId | string | No | |
eventId | string | Yes | |
customerId | string | Yes | |
milestone | string | Yes | |
status | "recorded", "revoked", "reinstated", "reversed" | Yes | |
programIds | array | Yes | |
rewardIds | array | Yes | |
count | integer | No | minimum 0 |
valueCents | integer | No | minimum 0. maximum 1000000000000 |
occurredAt | string | Yes | date-time |
decisions | array | Yes | |
restoredCents | integer | No | minimum 0. maximum 9007199254740991 |
partners[]
| Field | Type | Required | Details |
|---|---|---|---|
partners.partnerId | string | Yes | |
partners.partnerExternalId | string | Yes |
decisions[]
| Field | Type | Required | Details |
|---|---|---|---|
decisions.programId | string | Yes | |
decisions.programName | string | Yes | |
decisions.partnerId | string | No | |
decisions.outcome | "rewarded", "skipped" | Yes | |
decisions.reason | string | Yes | |
decisions.reasonParts | array | No | |
decisions.amountCents | integer | Yes | minimum 0. maximum 1000000000000 |
decisions.milestoneCount | integer | No | minimum 0 |
decisions.milestoneThreshold | integer | No | minimum 1. maximum 10000 |
decisions.reasonParts[]
| Field | Type | Required | Details |
|---|---|---|---|
decisions.reasonParts.code | string | Yes | |
decisions.reasonParts.params | object | No |
Payload schema
json
{
"type": "object",
"properties": {
"partners": {
"type": "array",
"items": {
"type": "object",
"properties": {
"partnerId": {
"type": "string",
"minLength": 1
},
"partnerExternalId": {
"type": "string",
"minLength": 1
}
},
"required": [
"partnerId",
"partnerExternalId"
],
"additionalProperties": true
}
},
"partnerId": {
"type": "string",
"minLength": 1
},
"partnerExternalId": {
"type": "string",
"minLength": 1
},
"eventId": {
"type": "string",
"minLength": 1
},
"customerId": {
"type": "string",
"minLength": 1
},
"milestone": {
"type": "string",
"minLength": 1
},
"status": {
"type": "string",
"enum": [
"recorded",
"revoked",
"reinstated",
"reversed"
]
},
"programIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"rewardIds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"count": {
"type": "integer",
"minimum": 0
},
"valueCents": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000000
},
"occurredAt": {
"type": "string",
"format": "date-time"
},
"decisions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"programId": {
"type": "string",
"minLength": 1
},
"programName": {
"type": "string"
},
"partnerId": {
"type": "string",
"minLength": 1
},
"outcome": {
"type": "string",
"enum": [
"rewarded",
"skipped"
]
},
"reason": {
"type": "string"
},
"reasonParts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string",
"minLength": 1
},
"params": {
"type": "object",
"additionalProperties": {}
}
},
"required": [
"code"
],
"additionalProperties": true
}
},
"amountCents": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000000
},
"milestoneCount": {
"type": "integer",
"minimum": 0
},
"milestoneThreshold": {
"type": "integer",
"minimum": 1,
"maximum": 10000
}
},
"required": [
"programId",
"programName",
"outcome",
"reason",
"amountCents"
],
"additionalProperties": true
}
},
"restoredCents": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"partners",
"eventId",
"customerId",
"milestone",
"status",
"programIds",
"rewardIds",
"occurredAt",
"decisions"
],
"additionalProperties": true
}Handle delivery
Verify the signature over the raw body before parsing JSON. Check that the body ID matches Grizzlee-Event-Id. Process each ID once, since delivery can repeat.