Verify engagement
Track a creator's attempt through the state machine, trigger verification of a comment or like, and use the admin routes to force, look up, or batch-verify.
An attempt is one creator's run at one challenge. You create it, advance it as the creator acts, and the API verifies the comment or like in the background. The outcome reaches you through webhooks.
States
created → target_opened → verification_pending → verified → rewarded
↓
failed_retryable → (retry) → verification_pending
↓
failed_terminal| State | Meaning |
|---|---|
created | Attempt exists; the creator has not opened the post yet |
target_opened | The creator viewed the post |
verification_pending | The creator claimed completion; verification is running |
verified | A matching comment or like was found. creator.verified sent |
rewarded | Points issued. reward.issued sent |
failed_retryable | No match yet; the attempt can be claimed again. verification.failed with reason: "retryable" |
failed_terminal | Retries exhausted or the post cannot be read. verification.failed with reason: "terminal" |
awaiting_claim also appears in the state enum. Treat it like target_opened: claim_completion is accepted from it.
Actions
PATCH …/attempts/{attemptId} takes one action:
action | Allowed from | Moves to |
|---|---|---|
target_opened | created, target_opened | target_opened |
claim_completion | target_opened, awaiting_claim, failed_retryable | verification_pending |
Any other combination returns 409. In particular, claim_completion straight from created is rejected: send target_opened first.
Run an attempt for a creator
The admin routes act on behalf of a registered creator, identified by userId or externalId (exactly one).
1. Create the attempt
POST /admin/challenges/{challengeId}/attempts
curl -X POST {API_BASE_URL}/admin/challenges/1/attempts \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{ "externalId": "cust_abc123" }'{
"data": {
"id": 39,
"userId": 42,
"challengeId": 1,
"state": "created",
"retryCount": 0,
"maxRetries": 10,
"lastVerificationAt": null,
"verificationDeadline": null,
"stateHistory": [],
"metadata": null,
"createdAt": "2026-06-02T15:00:11.000Z",
"updatedAt": "2026-06-02T15:00:11.000Z"
}
}| Status | error | Meaning |
|---|---|---|
201 | Created | |
400 | Neither userId nor externalId given | |
404 | Creator or challenge not found | |
409 | ACTIVE_ATTEMPT_EXISTS | The creator already has an open attempt on this challenge |
409 | ALREADY_REWARDED | The creator was already rewarded for this challenge |
2. Mark the post opened
PATCH /admin/challenges/{challengeId}/attempts/{attemptId}
curl -X PATCH {API_BASE_URL}/admin/challenges/1/attempts/39 \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{ "action": "target_opened" }'{ "data": { "id": 39, "state": "target_opened" } }3. Claim completion
Same route with { "action": "claim_completion" }. The attempt moves to verification_pending and verification starts. 409 STATE_CONFLICT or 409 VERIFICATION_IN_PROGRESS means the state changed under you; re-read the attempt.
4. Wait for the result
Verification is asynchronous. Subscribe to creator.verified, reward.issued, and verification.failed on your webhook, or poll:
GET /admin/challenges/{challengeId}/attempts/{attemptId}
{
"data": {
"id": 39,
"state": "verified",
"retryCount": 0,
"maxRetries": 10,
"matchedComment": { "commentText": "Love this product! #SummerVibes", "commentedAt": "2026-06-02T15:04:40.000Z" },
"pointsAwarded": null,
"createdAt": "2026-06-02T15:00:11.000Z",
"updatedAt": "2026-06-02T15:05:02.000Z"
}
}A failed_retryable attempt accepts claim_completion again, up to maxRetries.
Creator-side routes
POST /challenges/{challengeId}/attempts and PATCH /challenges/{challengeId}/attempts/{attemptId} do the same thing for the creator's own token, resolving the creator from the token instead of the body. Use them when creators call your API directly.
What counts as a match
Comment challenges
The attempt verifies when the post has a comment whose username equals the creator's linked handle for the challenge's platform and that was posted after the attempt was created. The matched text is returned as matchedComment and in the creator.verified webhook as commentText.
Like challenges
Create the campaign with challenge.action: "like" (Instagram only). On claim_completion, the API ensures a fresh likers fetch exists for the post, waits for it to finish, and verifies when the creator's linked handle appears in the liker set. The comparison is case-insensitive. If the fetch does not finish in time or fails, the attempt fails with the usual retryable or terminal semantics.
Because the public liker fetch can return fewer profiles than Instagram's visible like count, a creator who did like the post may still not verify. Use force-verify for those cases after checking manually.
Admin tools
Look up by handle
GET /admin/challenges/{challengeId}/attempts/by-handle/{handle}
Returns the creator's attempt for the challenge (Attempt shape as above) or null. The handle may include @.
Force-verify
POST /admin/challenges/{challengeId}/attempts/{attemptId}/force-verify
Moves any attempt that is not yet verified, rewarded, or failed_terminal straight to verified, schedules the reward, and sends creator.verified followed by reward.issued.
{ "data": { "id": 39, "state": "verified", "previousState": "verification_pending" } }409 if the attempt is already verified or rewarded.
Batch verify and reward
POST /admin/campaigns/{id}/verify-and-reward
For every registered creator whose handle appears in the stored comments of the campaign's post and who has no attempt yet: creates an attempt in verified, schedules the reward, and sends the webhooks. Refresh comments first so the stored set is current.
{ "data": { "matched": 15, "verified": 14, "rewarded": 14 } }Redeliver webhooks
POST /admin/challenges/{challengeId}/attempts/{attemptId}/redeliver-webhooks
Re-sends the webhooks for an attempt after a receiver outage. Optional body { "events": ["creator.verified"] }; omit it to resend every event the attempt's current state qualifies for.