Skip to main content
OTP (One-Time Password) provides an extra layer of customer identity verification. OTP is NOT required for all partners — each integration has its own policy configured by Zupy during onboarding.
Prerequisites: Your API key (zupy_pk_*) and familiarity with the Authentication page. OTP is an additional verification layer on top of API key auth.

OTP Policy Per Integration

Each integration has its own OTP policy, configured by Zupy during onboarding. The policy determines which customer-facing actions require identity verification.

Policy Settings

SettingDescription
require_otp_for_enrollmentVerify identity before enrolling in loyalty program
require_otp_for_redemptionVerify identity before redeeming rewards
require_otp_for_coupon_usageVerify identity before using coupons
trust_partner_validationTrust that the partner already verified the customer

Trust Levels

Zupy defines three trust level presets:
LevelEnrollment OTPRedemption OTPCoupon OTPTrust PartnerUse Case
StrictRequiredRequiredRequiredNoNew or unknown partners (default)
RelaxedNot requiredRequiredRequiredNoTablet/kiosk integrations (e.g., Goomer)
TrustedNot requiredNot requiredNot requiredYesVerified CRMs with existing identity checks (e.g., Repediu)
PartnerTypeTrust LevelRationale
RepediuCRMTrustediFood/Rappi already verify customer identity — orders come from authenticated delivery platforms
GoomerTabletRelaxedOpen tablet in restaurant — customer types their phone number, no prior identity verification
SaiposPOSRelaxedPOS identifies customer by phone, but coupon usage at checkout needs confirmation
Your trust level is assigned during onboarding based on your integration type and how you verify customer identity.

When OTP Is Required vs. Not

ActionOTP Possible?Depends On
Search customersNever
View points balance / historyNever
Award pointsNeverB2B operation, no customer interaction
List rewards / couponsNever
View loyalty programsNever
Send webhookNever
Redeem rewardPer configrequire_otp_for_redemption
Validate / use couponPer configrequire_otp_for_coupon_usage
Enroll customerPer configrequire_otp_for_enrollment
Most read operations never require OTP. Only write operations that directly affect a customer’s balance or benefits may require verification, depending on your policy.

The OTP Flow

When your integration requires OTP for an action, follow this 3-step flow:
1

Request OTP

Send the customer’s identifier (phone, email, or CPF) to request a verification code.
POST /api/v2/auth/request-otp/
Zupy sends a 6-digit code to the customer via WhatsApp (primary) or email (fallback). The code expires in 5 minutes.
2

Verify OTP

The customer provides the code. Send it back to Zupy for verification.
POST /api/v2/auth/verify-otp/
On success, you receive an otp_session token along with the customer’s profile.
3

Use OTP Session

Include the session token in subsequent requests that require OTP.
X-OTP-Session: {otp_session_token}
The session is valid for 30 minutes. After expiry, request a new OTP.

Step 1: Request OTP

POST /api/v2/auth/request-otp/
Auth: API Key (X-API-Key) Request Body
FieldTypeRequiredDescription
identifierstringYesCustomer’s phone (+5511987654321), email, or CPF
Response — 200 OK
{
  "data": {
    "status": "sent",
    "channel": "whatsapp",
    "expires_in": 300
  },
  "meta": {}
}
curl -X POST "https://api.zupy.com/api/v2/auth/request-otp/" \
  -H "X-API-Key: zupy_pk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"identifier": "+5511987654321"}'

Step 2: Verify OTP

POST /api/v2/auth/verify-otp/
Auth: API Key (X-API-Key) Request Body
FieldTypeRequiredDescription
identifierstringYesSame identifier used in Step 1
otp_codestringYes6-digit code the customer received
Response — 200 OK
{
  "data": {
    "customer_id": "2awTHloSJX7kGGprFerOOsvABcd",
    "is_new": false,
    "otp_session": "abc123def456ghi789...",
    "full_name": "Maria Santos",
    "points_balance": 150,
    "tier": "silver"
  },
  "meta": {}
}
curl -X POST "https://api.zupy.com/api/v2/auth/verify-otp/" \
  -H "X-API-Key: zupy_pk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"identifier": "+5511987654321", "otp_code": "123456"}'

Verify OTP — Errors

StatusTypeWhen
400validation-errorMissing identifier or otp_code
401authentication-requiredInvalid or expired OTP code
404not-foundOTP was not requested for this identifier
429rate-limit-exceededMaximum verification attempts reached

Step 3: Use the OTP Session

Include the X-OTP-Session header in requests that require OTP verification:
curl -X POST "https://api.zupy.com/api/v2/customers/{id}/rewards/{rid}/redeem/" \
  -H "X-API-Key: zupy_pk_your_api_key_here" \
  -H "X-OTP-Session: abc123def456ghi789..." \
  -H "Content-Type: application/json" \
  -d '{"use_z_tokens": false}'

Session Expiry

The OTP session token is valid for 30 minutes (cache-backed). After expiry:
  • Requests with the expired token return 403 with type otp-required
  • Request a new OTP to get a fresh session

What Happens Without OTP

If your integration’s OTP policy requires verification for an action and you don’t provide a valid X-OTP-Session header, you receive a 403 error:
{
  "type": "https://api.zupy.com/errors/otp-required",
  "title": "OTP Required",
  "status": 403,
  "detail": "Customer OTP verification required for this action"
}
This error only occurs when your specific integration’s policy requires OTP for the attempted action. If your policy doesn’t require OTP (e.g., trusted partners), you won’t see this error.

Full OTP Example

Complete end-to-end flow: request OTP, verify, then redeem a reward.
import requests

BASE_URL = "https://api.zupy.com/api/v2"
HEADERS = {"X-API-Key": "zupy_pk_your_api_key_here"}

# Step 1: Request OTP
response = requests.post(
    f"{BASE_URL}/auth/request-otp/",
    json={"identifier": "+5511987654321"},
    headers=HEADERS,
)
print(response.json())
# {"data": {"status": "sent", "channel": "whatsapp", "expires_in": 300}, "meta": {}}

# Step 2: Customer provides the code → Verify
otp_code = input("Enter the code sent to customer: ")
response = requests.post(
    f"{BASE_URL}/auth/verify-otp/",
    json={"identifier": "+5511987654321", "otp_code": otp_code},
    headers=HEADERS,
)
verify_data = response.json()["data"]
otp_session = verify_data["otp_session"]
customer_id = verify_data["customer_id"]

# Step 3: Redeem reward with OTP session
reward_id = "2awTHnPw8X7kGGpr..."
response = requests.post(
    f"{BASE_URL}/customers/{customer_id}/rewards/{reward_id}/redeem/",
    json={"use_z_tokens": False},
    headers={**HEADERS, "X-OTP-Session": otp_session},
)
coupon = response.json()["data"]
print(f"Coupon code: {coupon['coupon_code']}")

Next Steps

Webhook Setup

Configure webhooks for automatic order processing

Partner Onboarding

Complete the onboarding checklist for production deployment

API Reference

Browse all endpoints with request/response schemas