Event Fund Claims
Overview
When an event organizer sells tickets, revenue is held in escrow. A Fund Claim is a formal request to release a portion of that escrowed revenue into the organizer's wallet. Every claim goes through a PENDING → APPROVED / REJECTED lifecycle, and funds only move on explicit admin approval.
Authentication: All endpoints require Authorization: Bearer <jwt_token>
Business Rules
| Rule | Detail |
|---|---|
| One pending per event | At most ONE PENDING claim per event at any time |
| Claimable amount formula | totalRevenue − totalRefunded − totalClaimed − totalPendingClaims |
| Admin-only for active events | If event has not ended, only ADMIN can initiate a claim |
| Refund deadline | 3 days before event start. Past this → no refunds, organizer may claim early |
| Safe ratio (pre-deadline) | Only up to 80% of net revenue claimable before deadline; 20% held as refund reserve |
| Escrow release | Approval atomically releases escrow → organizer wallet. No partial releases |
| Admin claims require note | adminNote is mandatory on admin-initiated claims |
| Refund safety | Refunds and approvals use pessimistic write lock on EscrowAccount to prevent race conditions |
Claim Status Lifecycle
| Status | Meaning |
|---|---|
PENDING |
Submitted, awaiting admin review. Escrow NOT yet released |
APPROVED |
Admin approved. Escrow atomically released to organizer wallet |
REJECTED |
Admin rejected. No funds moved. Organizer may submit a new claim |
CANCELLED |
Organizer cancelled before admin action. No funds moved |
Standard Response Format
Success
{
"success": true,
"httpStatus": "OK",
"message": "Fund claim submitted successfully",
"action_time": "2026-04-27T10:30:45",
"data": { }
}
Error
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "A pending claim already exists for this event",
"action_time": "2026-04-27T10:30:45",
"data": "A pending claim already exists for this event"
}
Endpoints
1. List All Claims (Admin)
Endpoint: GET /api/v1/e-events/claims
Access: 🔒 ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN
Purpose: Returns all fund claims across all events. Optionally filter by status.
Query Parameters:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
status |
ClaimStatus enum |
No | Filter by: PENDING, APPROVED, REJECTED, CANCELLED |
— (all returned) |
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Fund claims retrieved",
"data": [
{
"claimId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"claimNumber": "EFC-2026-000001",
"eventId": "uuid",
"eventTitle": "Dar Jazz Night",
"eventStatus": "PUBLISHED",
"organizerId": "uuid",
"organizerName": "Amina Hassan",
"status": "PENDING",
"claimedAmount": 50000.00,
"totalRevenueSnapshot": 80000.00,
"totalRefundedSnapshot": 5000.00,
"totalPreviouslyClaimedSnapshot": 0.00,
"totalPendingAtSubmission": 0.00,
"currency": "TZS",
"adminInitiated": false,
"adminId": null,
"adminNote": null,
"organizerNote": "Requesting first partial claim",
"reviewedById": null,
"reviewerName": null,
"reviewNote": null,
"reviewedAt": null,
"escrowsReleasedCount": 0,
"escrowsSkippedCount": 0,
"actualReleasedAmount": null,
"initiatedAt": "2026-04-20T08:00:00",
"updatedAt": "2026-04-20T08:00:00"
}
]
}
Response Fields:
| Field | Type | Description |
|---|---|---|
claimId |
UUID |
Unique claim identifier |
claimNumber |
string |
Human-readable reference, e.g. EFC-2026-000001 |
eventId |
UUID |
The event this claim belongs to |
eventTitle |
string |
Display name of the event |
eventStatus |
EventStatus |
Current event status |
organizerId |
UUID |
Organizer's account ID |
organizerName |
string |
Organizer's display name |
status |
ClaimStatus |
Current claim status |
claimedAmount |
BigDecimal |
Amount the organizer requested |
totalRevenueSnapshot |
BigDecimal |
Organizer's net revenue at time of submission |
totalRefundedSnapshot |
BigDecimal |
Total refunds issued at time of submission |
totalPreviouslyClaimedSnapshot |
BigDecimal |
Already released to wallet before this claim |
totalPendingAtSubmission |
BigDecimal |
Other pending claim amounts at submission time |
currency |
string |
Currency code, e.g. TZS |
adminInitiated |
boolean |
true if an admin submitted on behalf of organizer |
adminId |
UUID |
Admin's account ID (null if organizer-initiated) |
adminNote |
string |
Admin's reason for initiating (null if organizer-initiated) |
organizerNote |
string |
Optional note from organizer |
reviewedById |
UUID |
ID of admin who reviewed (null if pending) |
reviewerName |
string |
Name of reviewing admin (null if pending) |
reviewNote |
string |
Admin's review note (null if pending) |
reviewedAt |
LocalDateTime |
Timestamp of review (null if pending) |
escrowsReleasedCount |
Integer |
Number of escrow entries released on approval |
escrowsSkippedCount |
Integer |
Escrow entries skipped (e.g. already refunded) |
actualReleasedAmount |
BigDecimal |
Actual amount credited to wallet on approval |
initiatedAt |
LocalDateTime |
When the claim was submitted |
updatedAt |
LocalDateTime |
Last updated timestamp |
Possible Errors:
| HTTP Status | Description |
|---|---|
401 UNAUTHORIZED |
Invalid or expired JWT |
403 FORBIDDEN |
Caller does not have admin role |
2. Get My Claims (Organizer)
Endpoint: GET /api/v1/e-events/claims/my-claims
Access: 🔒 Authenticated organizer (any role)
Purpose: Returns all fund claims submitted by the currently authenticated organizer, across all their events.
No query parameters.
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "My fund claims retrieved",
"data": [ /* array of EventFundClaimResponse — same structure as endpoint 1 */ ]
}
Possible Errors:
| HTTP Status | Description |
|---|---|
401 UNAUTHORIZED |
Invalid or expired JWT |
3. Get Claim by ID
Endpoint: GET /api/v1/e-events/claims/{claimId}
Access: 🔒 Authenticated — admin sees any claim; organizer sees only their own
Purpose: Returns full details of a single fund claim.
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
claimId |
UUID |
Yes | The claim's unique identifier |
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Claim retrieved",
"data": { /* EventFundClaimResponse */ }
}
Possible Errors:
| HTTP Status | Description |
|---|---|
401 UNAUTHORIZED |
Invalid or expired JWT |
403 FORBIDDEN |
Organizer attempting to view another organizer's claim |
404 NOT_FOUND |
Claim with given ID does not exist |
4. Get Event Claims
Endpoint: GET /api/v1/e-events/claims/event/{eventId}
Access: 🔒 Authenticated — organizer (own events) or admin (any event)
Purpose: Returns all claims ever submitted for a specific event.
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
eventId |
UUID |
Yes | The event's unique identifier |
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Event claims retrieved",
"data": [ /* array of EventFundClaimResponse */ ]
}
Possible Errors:
| HTTP Status | Description |
|---|---|
401 UNAUTHORIZED |
Invalid or expired JWT |
403 FORBIDDEN |
Organizer does not own this event |
404 NOT_FOUND |
Event not found |
5. Get Claimable Amount
Endpoint: GET /api/v1/e-events/claims/event/{eventId}/claimable-amount
Access: 🔒 Authenticated — organizer (own events) or admin
Purpose: Returns the current claimable amount breakdown for an event, including eligibility status and the active pending claim if one exists.
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
eventId |
UUID |
Yes | The event's unique identifier |
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Claimable amount retrieved",
"data": {
"eventId": "uuid",
"eventTitle": "Dar Jazz Night",
"totalRevenue": 80000.00,
"totalRefunded": 5000.00,
"totalClaimed": 0.00,
"totalPendingClaims": 0.00,
"claimableAmount": 60000.00,
"currency": "TZS",
"eligible": true,
"ineligibilityReason": null,
"activePendingClaimId": null,
"refundDeadline": "2026-05-10T00:00:00+03:00",
"pastRefundDeadline": false
}
}
Note on claimable amount:
claimableAmount = totalRevenue − totalRefunded − totalClaimed − totalPendingClaims. Before the refund deadline, only 80% of net revenue is claimable. After the deadline, 100% is claimable.
Response Fields:
| Field | Type | Description |
|---|---|---|
totalRevenue |
BigDecimal |
Net organizer share of all non-refunded tickets |
totalRefunded |
BigDecimal |
Gross amount returned to buyers |
totalClaimed |
BigDecimal |
Already released to organizer wallet |
totalPendingClaims |
BigDecimal |
Locked by current PENDING claims |
claimableAmount |
BigDecimal |
What the organizer can claim right now |
eligible |
boolean |
Whether a new claim can be submitted |
ineligibilityReason |
string |
Human-readable reason if eligible = false |
activePendingClaimId |
UUID |
ID of the existing pending claim, or null |
refundDeadline |
ZonedDateTime |
eventStartDate − 3 days |
pastRefundDeadline |
boolean |
true = refund window closed, full amount claimable |
Possible Errors:
| HTTP Status | Description |
|---|---|
401 UNAUTHORIZED |
Invalid or expired JWT |
403 FORBIDDEN |
Organizer does not own this event |
404 NOT_FOUND |
Event not found |
6. Get Revenue Summary (Admin)
Endpoint: GET /api/v1/e-events/claims/event/{eventId}/revenue-summary
Access: 🔒 ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN
Purpose: Returns a full financial summary of an event including gross sales, platform fees, refunds, claimed amounts, and escrow balance. Used for admin audit and oversight.
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
eventId |
UUID |
Yes | The event's unique identifier |
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Event revenue summary retrieved",
"data": {
"eventId": "uuid",
"eventTitle": "Dar Jazz Night",
"grossRevenue": 100000.00,
"totalRefunded": 5000.00,
"platformFees": 10000.00,
"netOrganizerRevenue": 85000.00,
"totalClaimed": 0.00,
"totalPendingClaims": 0.00,
"escrowBalance": 85000.00,
"currency": "TZS"
}
}
Possible Errors:
| HTTP Status | Description |
|---|---|
401 UNAUTHORIZED |
Invalid or expired JWT |
403 FORBIDDEN |
Caller does not have admin role |
404 NOT_FOUND |
Event not found |
7. Submit Claim (Organizer)
Endpoint: POST /api/v1/e-events/claims/event/{eventId}
Access: 🔒 Authenticated organizer — must own the event
Purpose: Organizer submits a fund claim for the full current claimable amount. The claim amount is computed server-side — organizer cannot override it.
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
eventId |
UUID |
Yes | The event to submit a claim for |
Request Body (optional):
{
"organizerNote": "Requesting first partial claim before event ends"
}
Request Body Fields:
| Field | Type | Required | Description | Validation |
|---|---|---|---|---|
organizerNote |
string |
No | Optional note from the organizer | Max 1000 characters |
Guard Rules Applied Server-Side:
- Event must belong to the authenticated organizer
- Event must have ended OR
pastRefundDeadline = true(otherwise only admin can claim) claimableAmountmust be > 0- No existing
PENDINGclaim for this event
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Fund claim submitted successfully",
"data": { /* EventFundClaimResponse with status: PENDING */ }
}
Possible Errors:
| HTTP Status | Description |
|---|---|
400 BAD_REQUEST |
A pending claim already exists for this event |
400 BAD_REQUEST |
Claimable amount is zero — nothing to claim |
400 BAD_REQUEST |
Event has not ended and refund deadline has not passed — only admin can claim |
401 UNAUTHORIZED |
Invalid or expired JWT |
403 FORBIDDEN |
Authenticated user does not own this event |
404 NOT_FOUND |
Event not found |
8. Admin-Initiate Claim
Endpoint: POST /api/v1/e-events/claims/event/{eventId}/admin-initiate
Access: 🔒 ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN
Purpose: Admin submits a fund claim on behalf of the organizer. Used when the event is still active (ticket sales ongoing). The claim is flagged adminInitiated = true with a mandatory audit note.
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
eventId |
UUID |
Yes | The event to claim funds for |
Request Body:
{
"adminNote": "Organizer requested early release. Event ending tomorrow, no pending refunds."
}
Request Body Fields:
| Field | Type | Required | Description | Validation |
|---|---|---|---|---|
adminNote |
string |
Yes | Mandatory reason for admin-initiated claim | Not blank |
Guard Rules Applied Server-Side:
claimableAmountmust be > 0- No existing
PENDINGclaim for this event - Claim is capped at
claimableAmount(never fulltotalRevenue)
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Admin-initiated claim created",
"data": {
"claimId": "uuid",
"claimNumber": "EFC-2026-000003",
"status": "PENDING",
"adminInitiated": true,
"adminId": "uuid-of-admin",
"adminNote": "Organizer requested early release. Event ending tomorrow, no pending refunds.",
"claimedAmount": 64000.00,
"currency": "TZS"
}
}
Possible Errors:
| HTTP Status | Description |
|---|---|
400 BAD_REQUEST |
A pending claim already exists for this event |
400 BAD_REQUEST |
Claimable amount is zero — nothing to claim |
401 UNAUTHORIZED |
Invalid or expired JWT |
403 FORBIDDEN |
Caller does not have admin role |
404 NOT_FOUND |
Event not found |
422 UNPROCESSABLE_ENTITY |
adminNote is blank |
9. Approve Claim (Admin)
Endpoint: POST /api/v1/e-events/claims/{claimId}/approve
Access: 🔒 ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN
Purpose: Admin approves a pending claim. This atomically acquires a pessimistic write lock on the EscrowAccount, validates available balance, releases escrow entries, and credits the organizer's wallet. The actualReleasedAmount may differ from claimedAmount if refunds were issued between submission and approval.
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
claimId |
UUID |
Yes | The claim to approve |
Request Body (optional):
{
"reviewNote": "Verified escrow balance. Approved for release."
}
Request Body Fields:
| Field | Type | Required | Description |
|---|---|---|---|
reviewNote |
string |
No | Optional note from reviewing admin |
Critical: Approval acquires a
PESSIMISTIC_WRITElock on theEscrowAccountrow. Concurrent refund operations for the same event will block until the approval transaction completes, preventing escrow from going negative.
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Fund claim approved and funds released",
"data": {
"claimId": "uuid",
"claimNumber": "EFC-2026-000001",
"status": "APPROVED",
"claimedAmount": 50000.00,
"actualReleasedAmount": 48000.00,
"escrowsReleasedCount": 12,
"escrowsSkippedCount": 1,
"reviewedById": "admin-uuid",
"reviewerName": "Admin John",
"reviewNote": "Verified escrow balance. Approved for release.",
"reviewedAt": "2026-04-27T14:30:00"
}
}
Note on
actualReleasedAmount: May be less thanclaimedAmountif one or more escrow entries were refunded between claim submission and admin approval. TheescrowsSkippedCountindicates how many entries were skipped for this reason.
Possible Errors:
| HTTP Status | Description |
|---|---|
400 BAD_REQUEST |
Claim is not in PENDING status |
400 BAD_REQUEST |
Escrow balance insufficient to cover claim amount |
401 UNAUTHORIZED |
Invalid or expired JWT |
403 FORBIDDEN |
Caller does not have admin role |
404 NOT_FOUND |
Claim not found |
10. Reject Claim (Admin)
Endpoint: POST /api/v1/e-events/claims/{claimId}/reject
Access: 🔒 ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN
Purpose: Admin rejects a pending claim. No funds are moved. The organizer may submit a new claim after rejection.
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
claimId |
UUID |
Yes | The claim to reject |
Request Body (optional):
{
"reviewNote": "Pending dispute investigation. Please resubmit after resolution."
}
Request Body Fields:
| Field | Type | Required | Description |
|---|---|---|---|
reviewNote |
string |
No | Reason for rejection (shown to organizer) |
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Fund claim rejected",
"data": {
"claimId": "uuid",
"claimNumber": "EFC-2026-000001",
"status": "REJECTED",
"reviewedById": "admin-uuid",
"reviewerName": "Admin John",
"reviewNote": "Pending dispute investigation. Please resubmit after resolution.",
"reviewedAt": "2026-04-27T14:35:00"
}
}
Possible Errors:
| HTTP Status | Description |
|---|---|
400 BAD_REQUEST |
Claim is not in PENDING status |
401 UNAUTHORIZED |
Invalid or expired JWT |
403 FORBIDDEN |
Caller does not have admin role |
404 NOT_FOUND |
Claim not found |
11. Cancel Claim (Organizer)
Endpoint: DELETE /api/v1/e-events/claims/{claimId}
Access: 🔒 Authenticated organizer — must be the claim owner
Purpose: Organizer cancels their own pending claim before any admin action. This frees up the pending amount, allowing a new claim to be submitted.
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
claimId |
UUID |
Yes | The claim to cancel |
No request body.
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Fund claim cancelled",
"data": null
}
Possible Errors:
| HTTP Status | Description |
|---|---|
400 BAD_REQUEST |
Claim is not in PENDING status (already reviewed) |
401 UNAUTHORIZED |
Invalid or expired JWT |
403 FORBIDDEN |
Organizer does not own this claim |
404 NOT_FOUND |
Claim not found |
Financial Safety: Refund & Claim Concurrency
This section explains how the system prevents money from being over-released when a refund and a claim approval happen simultaneously.
The Race Condition Risk
Revenue = 10,000
PENDING claim = 10,000
→ Refund of 2,000 processed concurrently
→ Claim approved → releases 10,000
→ Refund also goes out → escrow goes negative 💀
Protections in Place
| Layer | Mechanism |
|---|---|
| Refund deadline | No refunds allowed within 3 days of event. Eliminates risk entirely post-deadline. |
| Safe ratio | Only 80% of net revenue claimable before deadline. 20% locked as refund reserve. |
| Pessimistic DB lock | Both approveClaim() and processRefund() acquire PESSIMISTIC_WRITE lock on EscrowAccount row — serializing the two operations. |
| Balance check | Before any deduction, service verifies escrow.balance >= amount. Throws InsufficientEscrowException if not. |
| Snapshot divergence | actualReleasedAmount on APPROVED claims may be less than claimedAmount if escrow entries were refunded between submission and approval. |
Safe Claimable Amount Formula
Before refund deadline:
claimableAmount = (totalRevenue × 0.80) − totalRefunded − totalClaimed − totalPendingClaims
After refund deadline:
claimableAmount = totalRevenue − totalRefunded − totalClaimed − totalPendingClaims
Endpoint Quick Reference
| # | Method | Path | Access | Description |
|---|---|---|---|---|
| 1 | GET |
/claims |
Admin | List all claims (filter by status) |
| 2 | GET |
/claims/my-claims |
Organizer | Get my own claims |
| 3 | GET |
/claims/{claimId} |
Organizer / Admin | Get single claim by ID |
| 4 | GET |
/claims/event/{eventId} |
Organizer / Admin | Get all claims for an event |
| 5 | GET |
/claims/event/{eventId}/claimable-amount |
Organizer / Admin | Get claimable amount breakdown |
| 6 | GET |
/claims/event/{eventId}/revenue-summary |
Admin | Full event revenue summary |
| 7 | POST |
/claims/event/{eventId} |
Organizer | Submit a new claim |
| 8 | POST |
/claims/event/{eventId}/admin-initiate |
Admin | Admin-initiate claim for active event |
| 9 | POST |
/claims/{claimId}/approve |
Admin | Approve claim + release escrow |
| 10 | POST |
/claims/{claimId}/reject |
Admin | Reject claim |
| 11 | DELETE |
/claims/{claimId} |
Organizer | Cancel pending claim |
Standard Error Reference
| HTTP Status | Scenario |
|---|---|
400 BAD_REQUEST |
Business rule violation (duplicate pending, zero claimable, wrong status) |
401 UNAUTHORIZED |
Missing, expired, or malformed JWT |
403 FORBIDDEN |
Insufficient role or ownership violation |
404 NOT_FOUND |
Event or claim does not exist |
422 UNPROCESSABLE_ENTITY |
Validation failure (e.g. blank adminNote) |
500 INTERNAL_SERVER_ERROR |
Unexpected server error |