Gamify Loyalty Webhooks
Webhooks let you receive real-time HTTP notifications when something interesting happens in the Gamify Loyalty System: a mission is completed, a reward is claimed, a badge is earned. Optimove posts a JSON payload to a URL you control; your server validates it and reacts however you need.
Use a webhook when you want to:
- Mirror loyalty events into your CRM, data warehouse, or marketing tool
- Trigger downstream automations (emails, push notifications, SMS)
- Audit player activity in your own systems
Setting Up a Webhook
Open Settings → Webhook Configuration and click Add Webhook.
Form Fields
| Field | Required | Description |
|---|---|---|
| Name | Yes | Short, human-readable label (1–128 characters). Used only inside the Optimove UI. |
| URL | Yes | Fully qualified HTTPS endpoint on your server. |
| Description | No | Optional notes for your team. |
| Events | Yes | One or more event types to receive (see |
Validating the URL
Click Validate to verify reachability. Optimove sends a sample mission_completed payload to the URL with a 10-second timeout. The UI shows the HTTP status code returned by your endpoint.
No
x-api-keyis included in the Validate request, so you can safely point it at third-party listeners (webhook.site, requestbin, ngrok) without exposing your production key.Note that
x-api-keyis sent on every real delivery. If you used a test URL for validation, switch it back to your own endpoint before saving — otherwise the production key will be sent to the third-party listener.
Saving and Capturing the API Key
Click Save & Activate. A modal displays the generated x-api-key for this webhook.
Copy and store this value now — it is shown only once. After you close the modal, the key cannot be retrieved. You can only rotate it to generate a new one.
Managing the API Key
Every webhook gets a unique 64-character hex secret. Optimove attaches it as the x-api-key request header on every real delivery.
Validating on Your Server
- Read the
x-api-keyheader on each incoming request. - Compare it against the value you stored during creation. Use a constant-time comparison to avoid timing attacks.
- Reject any request whose header is missing or does not match.
How Optimove Stores the Key
The key is encrypted at rest with AES-256-GCM. Optimove never logs or returns the plaintext after creation — the only way to see a value again is to rotate.
Rotating the Key
Rotate if the current key is compromised, or on a regular schedule:
- Open the webhook in Settings → Webhook Configuration and click Edit.
- Click Rotate API key and confirm in the modal.
- A new plaintext key appears in the same one-time modal. Copy it immediately.
The old key stops working immediately. Update the value on your receiving server before the next event arrives, or deliveries will start failing authentication.
Managing Existing Webhooks
Editing
Change Name, URL, Description, Events, or brand scope at any time. The API key is not rotated by editing — only Rotate API key changes it.
Disabling
Toggle the webhook to inactive. No further events are sent, but the configuration and key are preserved. Re-enable at any time.
Deleting
Permanently removes the webhook and its API key. This cannot be undone.
Outgoing Request Format
Every real delivery is an HTTPS POST.
Headers
| Header | Example Value | Notes |
|---|---|---|
Content-Type | application/json | Always JSON. |
X-Webhook-Event-Type | mission_completed | Use this to route the request without parsing the body. |
X-Webhook-Delivery-Id | a1b2c3d4-e5f6-7890-abcd-ef1234567890 | Unique per delivery attempt. Use for idempotency and logging. |
x-api-key | 9c8b7a6d… (64 hex chars) | Per-webhook shared secret. Validate on every request. |
Body
The body is the event payload directly: no envelope, no nested payload field. The event type is in the X-Webhook-Event-Type header, not the body.
Delivery Semantics
Timeouts and Success
Optimove waits up to 10 seconds for your server to respond. Any HTTP 2xx status counts as delivered.
Retries
A delivery is retried only on transient failures:
- HTTP status
408,429,500,502,503, or504 - Request timeout
- Network or connection error
Up to 5 retries with increasing delays:
- 2 seconds
- 8 seconds
- 30 seconds
- 2 minutes
- 10 minutes
After the final retry, the delivery is dropped.
Other
4xxresponses (400,401,403,404, etc.) are not retried — they are recorded as failures immediately. Make sure your endpoint returns one of the retryable codes (or lets the request time out) when you need Optimove to back off and retry.
Ordering and At-Least-Once
Deliveries are not strictly ordered. Use created_at in the payload if you need to reconstruct sequence. Because of retries, your endpoint may receive the same X-Webhook-Delivery-Id more than once — make your handlers idempotent.
Event Reference
Common Payload Fields
All payloads share these base fields:
| Field | Type | Description |
|---|---|---|
tenant_id | number | Optimove tenant identifier. |
brand_id | string | Brand identifier the event belongs to. Treat as an opaque string. |
player_id | string | Player identifier the event is about. Treat as an opaque string — Optimove does not enforce a specific format. |
created_at | number | Unix epoch milliseconds when the event occurred. |
Event-specific fields are listed under each event below.
mission_completed
mission_completedSent when a player finishes all objectives of a mission.
Event-specific fields:
| Field | Type | Description |
|---|---|---|
objective_id | string | Unique identifier of the completed mission. |
status | string | Completion status, for example "completed". |
Sample payload:
{
"tenant_id": 125,
"brand_id": "3f7a9c12-8b4e-4d2a-9f1c-6e5b8a2d4c11",
"player_id": "8d2e1f4a-7b6c-4d5e-9f8a-1b2c3d4e5f6a",
"created_at": 1747665138000,
"objective_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "completed"
}reward_claimed_from_mission
reward_claimed_from_missionSent when a player claims the reward attached to a completed mission.
Event-specific fields:
| Field | Type | Description |
|---|---|---|
objective_id | string | Unique identifier of the source mission. |
rewards | array | List of reward objects. Each object contains rewardTypeId (string), playerId (string), and amount (number). |
Sample payload:
{
"tenant_id": 125,
"brand_id": "3f7a9c12-8b4e-4d2a-9f1c-6e5b8a2d4c11",
"player_id": "8d2e1f4a-7b6c-4d5e-9f8a-1b2c3d4e5f6a",
"created_at": 1747665302000,
"objective_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"rewards": [
{
"rewardTypeId": "9c8b7a6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"playerId": "8d2e1f4a-7b6c-4d5e-9f8a-1b2c3d4e5f6a",
"amount": 100
}
]
}reward_claimed_from_leaderboard
reward_claimed_from_leaderboardSent when a player claims a reward earned through a leaderboard placement.
Event-specific fields:
| Field | Type | Description |
|---|---|---|
leaderboard_id | string | Unique identifier of the source leaderboard. |
rewards | array | List of reward objects. Same shape as reward_claimed_from_mission. |
Sample payload:
{
"tenant_id": 125,
"brand_id": "3f7a9c12-8b4e-4d2a-9f1c-6e5b8a2d4c11",
"player_id": "8d2e1f4a-7b6c-4d5e-9f8a-1b2c3d4e5f6a",
"created_at": 1747670445000,
"leaderboard_id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"rewards": [
{
"rewardTypeId": "7d6c5b4a-3e2f-1a0b-9c8d-7e6f5a4b3c2d",
"playerId": "8d2e1f4a-7b6c-4d5e-9f8a-1b2c3d4e5f6a",
"amount": 50
}
]
}badge_earned
badge_earnedSent when a player earns a badge.
Event-specific fields:
| Field | Type | Description |
|---|---|---|
badge_id | string | Unique identifier of the badge. |
badge_name | string | Display name of the badge, for example "Gold Explorer". |
Sample payload:
{
"tenant_id": 125,
"brand_id": "3f7a9c12-8b4e-4d2a-9f1c-6e5b8a2d4c11",
"player_id": "8d2e1f4a-7b6c-4d5e-9f8a-1b2c3d4e5f6a",
"created_at": 1747674729000,
"badge_id": "c4d5e6f7-a8b9-0123-cdef-456789012345",
"badge_name": "Gold Explorer"
}virtual_currency_added
virtual_currency_addedSent when virtual currency is credited to a player.
Event-specific fields:
| Field | Type | Description |
|---|---|---|
currency_id | string | Unique identifier of the virtual currency type. |
amount | number | Amount of virtual currency credited. |
Sample payload:
{
"tenant_id": 125,
"brand_id": "3f7a9c12-8b4e-4d2a-9f1c-6e5b8a2d4c11",
"player_id": "8d2e1f4a-7b6c-4d5e-9f8a-1b2c3d4e5f6a",
"created_at": 1747676730000,
"currency_id": "d5e6f7a8-b9c0-1234-def0-567890123456",
"amount": 500
}additional_reward_claimed
additional_reward_claimedSent when a player claims a standalone (additional) reward. Includes the source it originated from.
Event-specific fields:
| Field | Type | Description |
|---|---|---|
reward_id | string | Unique identifier of the reward. |
reward_name | string | Display name of the reward, for example "Bonus Points". |
short_code | string | Optional short code associated with the reward, for example "BONUS50". |
source | string | Origin of the reward: "mission" or "leaderboard". |
objective_id | string | Present when source is "mission". Unique identifier of the source mission. |
leaderboard_id | string | Present when source is "leaderboard". Unique identifier of the source leaderboard. |
Sample payload:
{
"tenant_id": 125,
"brand_id": "3f7a9c12-8b4e-4d2a-9f1c-6e5b8a2d4c11",
"player_id": "8d2e1f4a-7b6c-4d5e-9f8a-1b2c3d4e5f6a",
"created_at": 1747679211000,
"reward_id": "5b4a3c2d-1e0f-9a8b-7c6d-5e4f3a2b1c0d",
"reward_name": "Bonus Points",
"short_code": "BONUS50",
"source": "mission",
"objective_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Security Checklist
Before going live, verify your endpoint meets these requirements:
- Serve the endpoint over HTTPS only.
- Validate the
x-api-keyheader on every request; reject requests where the header is missing or does not match. - Never log the
x-api-keyvalue or include it in error reports. - Treat deliveries as at-least-once: deduplicate on
X-Webhook-Delivery-Id(or a domain-specific key) before applying side effects. - Respond quickly — well under 10 seconds. If your processing takes longer, acknowledge with
200immediately and queue the work.