Spend events
POST /v1/spend-events
A spend event is one card transaction, as your processor reports it. Send each one as it happens. Gave stores it before answering, then applies your programs.
Kinds
kind | When | Extra field |
|---|---|---|
authorization | The card was authorized. Gives an estimate, never a reward | |
incremental_authorization | More was authorized on an open authorization, such as a hotel extending its hold. amount is the increase, not the new total | authorizationId |
clearing | The transaction settled with its final amount. This is what earns a reward | authorizationId, optional |
reversal | An authorization was voided or expired before clearing | authorizationId |
refund | Money went back to the cardholder after clearing, in full or in part. amount is this refund's | originalTransactionId |
authorizationId and originalTransactionId are the externalTransactionId of the event they refer to. If an event arrives before the one it refers to, such as a refund before its clearing, Gave sets it aside and applies it once the original arrives.
A refund before your user claims the reward reduces it, in proportion. A refund after they claimed it is owed back, and recovered from their next claimable rewards (owed in the balance).
Fields
| Field | Type | What it is |
|---|---|---|
kind | string | One of the kinds above |
integratorId | string | Your integrator ID. It must be the key's |
userId | string | Your ID for the user |
externalTransactionId | string | Your processor's ID for the transaction |
amount.currency | string | ISO 4217 code, such as USD |
amount.amount | string | In the currency's minor unit (cents): up to 18 digits |
merchant.descriptor | string | As on the card transaction, 1 to 200 characters |
merchant.mcc | string | The 4-digit merchant category code |
merchant.mid | string, optional | The merchant ID, 1 to 64 characters |
merchant.country | string, optional | ISO 3166 alpha-2 code, such as US |
occurredAt | string | When it happened, in UTC. At most a minute ahead of Gave's clock |
USD spend earns rewards in USDC and EUR spend in EURe, one to one. Spend in other currencies is stored, and earns nothing for now.
{
"kind": "clearing",
"integratorId": "acme",
"userId": "alice",
"externalTransactionId": "txn_0001",
"amount": { "currency": "USD", "amount": "5000" },
"merchant": { "descriptor": "BLUE BOTTLE COFFEE", "mcc": "5814", "country": "US" },
"occurredAt": "2026-10-11T09:30:00.000Z"
}{
"kind": "refund",
"integratorId": "acme",
"userId": "alice",
"externalTransactionId": "txn_0002",
"originalTransactionId": "txn_0001",
"amount": { "currency": "USD", "amount": "2000" },
"merchant": { "descriptor": "BLUE BOTTLE COFFEE", "mcc": "5814", "country": "US" },
"occurredAt": "2026-10-12T15:00:00.000Z"
}Response
202 Accepted, once the event is stored:
{ "id": "se:acme:alice:clearing:txn_0001", "duplicate": false }Retries and duplicates
An event is identified by your integrator, the user, its kind and its externalTransactionId.
- The same event again is a duplicate:
202with"duplicate": true, and nothing changes. So after a timeout or a503, retry with the same body. - Different content under the same identity, such as another amount, is refused with
409 SpendEventConflict. It's never treated as a duplicate: fix the event, or its ID.
Errors
| Status | Error | Meaning |
|---|---|---|
400 | InvalidRequest | The body doesn't match the fields above |
403 | IntegratorMismatch | integratorId isn't your key's |
409 | SpendEventConflict | This event's ID was used with different content |
422 | EventInFuture | occurredAt is more than a minute ahead: check the sender's clock and time zone |
503 | Unavailable | Retry the same request |