Deduct Points
Remove points from a user's account when they redeem rewards.
⚠️
Critical: This operation must be idempotent. Check for duplicate yggRedemptionId values to prevent double deductions.
Endpoint Details
- Method:
POST - Path:
/deduct-points-by-address - Content-Type:
application/json
POST https://partner-api.example.com/deduct-points-by-addressRequest Format
Headers
| Header | Description | Example |
|---|---|---|
X-API-KEY | Your API key | your_api_key |
X-API-REQUEST | Unique UUID v7 | 01987d64-6519-747b-9200-beba98700464 |
X-API-SIGNATURE | HMAC signature | 92aa703cc483ea2cc90488c05e699179... |
Content-Type | Must be application/json | application/json |
Request Body
{
"address": "0xabcd....a",
"deductPoints": 1000,
"yggRedemptionId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}| Field | Type | Required | Description |
|---|---|---|---|
address | string | ✅ | User's wallet address |
deductPoints | integer | ✅ | Points to deduct (positive integer) |
yggRedemptionId | string | ✅ | YGG-generated UUID for tracking |
Response Format
Success Response
HTTP Status: 200 OK
Response Headers: Must include X-API-SIGNATURE
{
"success": true,
"partnerTransactionId": "txn_1234567890"
}Required Fields
| Field | Type | Description |
|---|---|---|
success | boolean | Must be true |
partnerTransactionId | string | Your unique transaction ID |
🏷️
Important: Generate a unique partnerTransactionId for this deduction. This ID will be used for potential reversals.
Implementation Guidelines
Idempotency Check
🚨
Must implement: Check for duplicate yggRedemptionId to prevent double deductions.
// Pseudocode for idempotency check
const existingTransaction = await findTransactionByRedemptionId(yggRedemptionId);
if (existingTransaction) {
return {
success: false,
errorCode: 'ERR-DUPLICATE-REQUEST',
errorMessage: 'This redemption has already been processed'
};
}Balance Validation
Always verify sufficient balance before deduction:
if (user.points < deductPoints) {
return {
success: false,
errorCode: 'ERR-INSUFFICIENT-POINTS',
errorMessage: `User has ${user.points} points, cannot deduct ${deductPoints}`
};
}Example Implementation
app.post('/deduct-points-by-address', async (req, res) => {
try {
// Validate authentication
if (!validateAuth(req.headers)) {
return res.json({
success: false,
errorCode: 'ERR-AUTH-FAILED',
errorMessage: 'Authentication failed'
});
}
const { address, deductPoints, yggRedemptionId } = req.body;
// Check for duplicate redemption
const existing = await findByRedemptionId(yggRedemptionId);
if (existing) {
return res.json({
success: false,
errorCode: 'ERR-DUPLICATE-REQUEST',
errorMessage: 'Redemption already processed'
});
}
// Find user and validate balance
const user = await findUserByWallet(address);
if (!user) {
return res.json({
success: false,
errorCode: 'ERR-USER-NOT-FOUND',
errorMessage: 'User not found'
});
}
if (user.points < deductPoints) {
return res.json({
success: false,
errorCode: 'ERR-INSUFFICIENT-POINTS',
errorMessage: 'Insufficient points'
});
}
// Perform deduction
const transactionId = await deductUserPoints(
user.id,
deductPoints,
yggRedemptionId
);
res.json({
success: true,
partnerTransactionId: transactionId
});
} catch (error) {
res.json({
success: false,
errorCode: 'ERR-INTERNAL',
errorMessage: 'Internal server error'
});
}
});Database Considerations
💾
Recommended: Store both yggRedemptionId and your partnerTransactionId for audit trails and potential reversals.
CREATE TABLE point_transactions (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
transaction_type VARCHAR(20) NOT NULL, -- 'deduct' or 'revert'
points INTEGER NOT NULL,
ygg_redemption_id VARCHAR(36) UNIQUE NOT NULL,
partner_transaction_id VARCHAR(50) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);