SpareSpare Docs
GuidesAPI Reference

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

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

StatusMeaningYour action
PendingAwaiting payer at the bankShow "continue to bank" UI; redirect to authorizationUrl
AuthorisedBank approved; ready for executionWait for payment settlement; fulfill when payment confirms
ConsumedConsent used for a completed paymentNo further action; archive for audit
RejectedPayer or bank declinedShow failure; create a new request if retrying
ExpiredNot authorized in time, or validity lapsedCreate a new consent; the old one can't be reused
RevokedCancelled by payer or your appStop 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)
}
ApproachProsCons
Poll on redirectSimpleRace if bank callback is slow
Poll with backoffReliable in sandboxWastes API calls at scale
WebhooksReal-time, efficientRequires endpoint + signature verification

Spare publishes events such as:

  • consent.created
  • consent.authorized
  • consent.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.

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

MistakeCorrect approach
Fulfilling on redirect aloneWait for Authorised + payment confirmation
Reusing expired consentCreate new consent from a new or existing request
Ignoring RejectedSurface clear error; offer retry with new authorization
Polling forever in productionRegister webhooks; poll only as fallback

API reference

Key takeaways

  • Treat consent status as the authorization gate, not the redirect callback.
  • Authorised means the bank approved; Consumed means the consent was used.
  • Use webhooks in production; poll get or sync in sandbox.

On this page