Consent Lifecycle
Payment consent states, transitions, and when to poll or use webhooks.
A payment consent is the durable record of what a payer authorized at their bank. Your application should treat consent status as the source of truth for whether you may execute or fulfill an order.
In this guide
- Every consent status and what triggers it
- How consent relates to payment requests
- When consent expires and how to refresh
- Polling vs webhooks for status updates
Why consent exists
Regulators require that payers explicitly approve payment instructions before money moves. Consent captures:
- Which payment request it applies to
- Which bank (provider) the payer used
- Authorization timestamps and expiry
- Whether the consent has been used, revoked, or rejected
You cannot execute a payment without a valid, authorized consent.
State machine
Status reference
| Status | Meaning | Your action |
|---|---|---|
Pending | Awaiting payer at the bank | Show "continue to bank" UI; redirect to authorizationUrl |
Authorised | Bank approved; ready for execution | Wait for payment settlement; fulfill when payment confirms |
Consumed | Consent used for a completed payment | No further action; archive for audit |
Rejected | Payer or bank declined | Show failure; create a new request if retrying |
Expired | Not authorized in time, or validity lapsed | Create a new consent; the old one can't be reused |
Revoked | Cancelled by payer or your app | Stop any pending fulfillment |
Status naming
API responses use PascalCase status strings (e.g. Authorised). Match exactly when comparing in code.
Lifecycle in context
1. Creation
Consent is always tied to a payment request:
const consent = await client.paymentConsents.createFromPaymentRequest({
paymentRequestId: request.data.id,
provider: { providerId, providerCode },
});consent = client.payment_consents.create_from_payment_request(
payment_request_id=request.data.id,
provider={"provider_id": provider_id, "provider_code": provider_code},
)var consent = client.paymentConsents().createFromPaymentRequest(
CreateFromPaymentRequestRequest.builder()
.paymentRequestId(request.getData().getId())
.provider(Provider.builder().providerId(providerId).providerCode(providerCode).build())
.build());var consent = await client.PaymentConsents.CreateFromPaymentRequestAsync(new CreateFromPaymentRequestRequest
{
PaymentRequestId = request.Data.Id,
Provider = new Provider { ProviderId = providerId, ProviderCode = providerCode },
});consent, err := client.PaymentConsents.CreateFromPaymentRequest(ctx, &spareapi.CreateFromPaymentRequestRequest{
PaymentRequestID: request.Data.ID,
Provider: &spareapi.Provider{ProviderID: providerID, ProviderCode: providerCode},
})
if err != nil {
log.Fatal(err)
}Payment request status typically moves to ConsentRaised.
2. Authorization
Redirect the payer to authorizationUrl. The bank handles authentication. Spare receives the callback and updates consent to Authorised or Rejected.
3. Execution
For instant payments, Spare executes after authorization. Consent becomes Consumed when the linked payment completes.
4. Expiry
Consents have a validity period (commonly up to 90 days for recurring mandates). After expiry:
- You cannot execute new payments under that consent
- Scheduled flows may need re-authorization
Use sync to refresh statuses in bulk when reconciling.
Keeping status current
Polling
Suitable for sandbox and simple flows:
curl "https://api.sandbox.tryspare.ae/consent/payment/CONSENT_ID" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-tenant: UAE"const consent = await client.paymentConsents.get({ consentId });consent = client.payment_consents.get(consent_id=consent_id)var consent = client.paymentConsents().get(consentId);var consent = await client.PaymentConsents.GetAsync(consentId);consent, err := client.PaymentConsents.Get(ctx, consentID)
if err != nil {
log.Fatal(err)
}Bulk refresh:
curl "https://api.sandbox.tryspare.ae/consent/payment/sync" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-tenant: UAE"await client.paymentConsents.sync();client.payment_consents.sync()client.paymentConsents().sync();await client.PaymentConsents.SyncAsync();err := client.PaymentConsents.Sync(ctx)
if err != nil {
log.Fatal(err)
}| Approach | Pros | Cons |
|---|---|---|
| Poll on redirect | Simple | Race if bank callback is slow |
| Poll with backoff | Reliable in sandbox | Wastes API calls at scale |
| Webhooks | Real-time, efficient | Requires endpoint + signature verification |
Webhooks (recommended for production)
Spare publishes events such as:
consent.createdconsent.authorizedconsent.revoked
See Webhooks for delivery and verification.
Always re-fetch before acting
Webhook payloads notify you of change, fetch the latest consent via API before fulfilling orders or triggering side effects.
Consent and mandates
For recurring or on-demand payment types, consent authorizes a series of debits rather than a single amount. The consent expiry defines the outer boundary for all scheduled or on-demand executions under that mandate.
Read Mandates for schedule β approve β execute flows.
Common integration mistakes
| Mistake | Correct approach |
|---|---|
| Fulfilling on redirect alone | Wait for Authorised + payment confirmation |
| Reusing expired consent | Create new consent from a new or existing request |
Ignoring Rejected | Surface clear error; offer retry with new authorization |
| Polling forever in production | Register webhooks; poll only as fallback |
API reference
- Payment Consents,
get,list,sync,update - Payment flow, where consent fits in the sequence
Key takeaways
- Treat consent
statusas the authorization gate, not the redirect callback. Authorisedmeans the bank approved;Consumedmeans the consent was used.- Use webhooks in production; poll
getorsyncin sandbox.