Code only
Submit a code with a phone number and consent.
For a campaign where a code is all a consumer needs: capabilities.receipt_required is false. One call enters them.
Minted codes and Seebait codes
unsupported, because only a submission can tell. And a Seebait submission can come back unknown with checking when Seebait does not answer in time: show the checking state and poll; it settles later. Test both with the sentinels; the replies have the same shape.1. When the page loads
Ask for the campaign. status tells you which screen to show; messages has the copy for every outcome in the consumer's language.
curl -s "$UTC_API_URL/api/v1/campaign" \
-H "Authorization: Bearer $UTC_API_KEY"If the consumer arrived by scanning a QR, the code is in the address. Check it before they fill anything in:
curl -s -X POST "$UTC_API_URL/api/v1/codes/check" \
-H "Authorization: Bearer $UTC_API_KEY" \
-H "Content-Type: application/json" \
-H "UTC-Client-IP: 196.41.60.77" \
-d '{"code":"TEST-0-ENTERED","phone":"0712345678"}'valid: show the form.invalidoralready_used: showmessageand stop.message_keysays which.unsupported: the campaign's codes are checked elsewhere; show the form and let the submission decide.
The check holds nothing. The submission checks the code again.
2. Submit
Mint the Idempotency-Key in the browser when the consumer presses submit, and forward it from your server. Why, and how.
curl -s -X POST "$UTC_API_URL/api/v1/submissions" \
-H "Authorization: Bearer $UTC_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"phone":"0712345678","code":"TEST-0-ENTERED","consent":true}'import { createClient, isUtcApiError } from "@utc/gateway-client"
const utc = createClient({ baseUrl: process.env.UTC_API_URL!, apiKey: process.env.UTC_API_KEY! })
export async function POST(req: Request) {
const idempotencyKey = req.headers.get("Idempotency-Key") ?? ""
const { phone, code, consent } = await req.json()
try {
const submission = await utc.submit({ phone, code, consent }, { idempotencyKey })
return Response.json(submission) // 200 for every outcome about the consumer
} catch (e) {
if (!isUtcApiError(e)) throw e
// The request was wrong, or the API could not be reached.
return Response.json({ error: { code: e.code, message: e.message, field: e.field } }, { status: e.status || 502 })
}
}3. Show the outcome
- Show
message. It is already in the consumer's language, with the campaign's own wording where it has any. - Branch on
message_keyfor anything more: a prize screen forinstant_win, a "try another code" button forinvalid. display_statusis the short label: Entered, Checking, Not accepted.- If
terminalis false, say the page can be closed, and poll.
curl -s "$UTC_API_URL/api/v1/submissions/$SUBMISSION_ID" \
-H "Authorization: Bearer $UTC_API_KEY"const settled = await utc.pollSubmission(submission.id, { maxSeconds: 60 })Every reply your page can meet is on Outcomes. To see each one, use the sentinels.
4. Errors
A 4xx means your request was wrong; nothing was recorded. The consumer did nothing wrong, so do not show them error.message: it is written for you. The ones a form can cause:
- 422
phone_invalid: validate the phone in the form first (+255…,255…or0…, then 6 or 7, then eight digits). - 422
consent_required: the box was not ticked. - 429
rate_limited: show therate_limitedmessage and waitRetry-Afterseconds. - A timeout or a 5xx after the client's retries: show
service_unavailable, keep the form and the key.