Skip to content

Payments and payment methods

A payment is money taken from a guest: a card charge, a BACS or SEPA Direct Debit collection, or any other method. A payment method (a mandate) is a card or Direct Debit saved on a booking so future payments can be taken. Lavanda sends a webhook when either one changes, and the API tells you what changed and why.

All events are in the webhook catalogue.

Event When it fires Payload
v1.payments.payment_method.created A saved payment method (card, BACS or SEPA mandate) is set up for a booking payment_method.id, booking.id, workspace.id, timestamp
v1.payments.payment_method.updated Its status changes: accepted, refused or revoked by the bank (BACS), cancelled, removed, card replaced by the bank, or guest switched banks (BACS) same as above
v2.payments.payment.created A payment is created, including a BACS collection from a mandate payment.id, workspace.id
v2.payments.payment.updated A payment’s status changes: succeeded, failed, cancelled, disputed, dispute won or lost. Also when the BACS expected debit date is set payment.id, workspace.id
v2.payments.payment.deleted A payment is removed payment.id, workspace.id
  • payment_method.id is the mandate ID.
  • Webhooks carry IDs only. Query the API for the details: the API response is your source of truth.
  • Webhooks can arrive out of order. Order payment_method deliveries by timestamp.
  • Your endpoint must be subscribed to v1.payments.payment_method.created and .updated to receive mandate updates. An endpoint subscribed only to payment.* events does not receive them.

Typical flow: Due → Success, or Due → Failed

  • Due — Created and in progress (for BACS, submitted to the bank and waiting to clear).
  • Success — The money was collected.
  • Failed — The payment didn’t go through. failureDetails.failureCode says why.
  • Canceled — Stopped before it completed. cancellationReason says why.
  • Disputed — The guest’s bank has raised a dispute (a chargeback or, for Direct Debit, an indemnity claim).
  • Dispute reversed — The dispute was lost and the payment reversed (reversalReason: CHARGEBACK). A won dispute returns the payment to Success.
  • Refunded and Expired — see Refunds and reversals. A refund that is later reversed by a refund reversal note shows as Failed.
Field Meaning
status DUE, SUCCESS, FAILED, CANCELED, DISPUTED, DISPUTE_REVERSED, REFUNDED, EXPIRED
initiatorType CIT (guest-initiated) or MIT (merchant-initiated, for example a collection from a mandate)
failureDetails.failureCode Why it failed, for example CARD_DECLINED, INSUFFICIENT_FUNDS, DEBIT_NOT_AUTHORIZED, ACCOUNT_CLOSED
cancellationReason For example REQUESTED_BY_CUSTOMER, ABANDONED, EXPIRED
reversalReason CHARGEBACK when a lost dispute reversed the payment
dispute reason (for example FRAUDULENT, DEBIT_NOT_AUTHORIZED) and status (for example NEEDS_RESPONSE, WON, LOST)
expectedDebitDate BACS only: the date the money is expected to leave the guest’s account
paymentMandate BACS only: the mandate the payment was collected from

Payments are looked up by workspace, so workspaceId is required. Pass workspace.id and payment.id from the webhook.

query Payment($filters: PaymentChargeFilters!) {
payments {
charges(first: 1, filters: $filters) {
edges {
node {
id
status
initiatorType
failureDetails { failureCode }
cancellationReason
reversalReason
dispute { reason status }
sourceDetails {
... on PaymentSourceGatewayDetails {
paymentMethodDetails {
paymentMethodType
... on PaymentBacsDebitDetails {
expectedDebitDate
paymentMandate { id }
}
}
}
}
}
}
}
}
}
{
"filters": {
"workspaceId": { "eq": "1234567890" },
"id": { "in": ["4c564450-4159-815b-81a4-000000190919"] }
}
}

Flow: Pending → Active → Cancelled

  • Pending — Being set up: waiting for the guest to authorise it, or for the bank to confirm.
  • Active — Ready to use for taking payments.
  • Cancelled — No longer usable. Terminal. If the payment method was removed at the payment provider, statusReason is PAYMENT_METHOD_DETACHED. If the guest completed the setup but the card could not be saved for future payments, statusReason is CARD_DECLINED_AT_SETUP, CARD_EXPIRED_AT_SETUP, CARD_SECURITY_CODE_INCORRECT_AT_SETUP or CARD_NUMBER_INCORRECT_AT_SETUP (UNKNOWN for any other card error). Send a new request.

BACS mandates also carry the bank’s own view of the mandate:

Field Values
networkStatus PENDING, ACCEPTED, REFUSED, REVOKED. Null for cards and SEPA
revocationReason ACCOUNT_CLOSED, BANK_ACCOUNT_RESTRICTED, BANK_OWNERSHIP_CHANGED, COULD_NOT_PROCESS, DEBIT_NOT_AUTHORIZED, or null

When a guest’s bank replaces their card, or a BACS guest switches banks, the mandate stays Active and its card or bank details are updated. You receive v1.payments.payment_method.updated.

Payment methods are looked up by booking, so there is no workspaceId filter. Pass booking.id and payment_method.id from the webhook. Access is checked against the booking’s workspace.

query PaymentMethod($filters: PaymentMandateFilters!) {
payments {
paymentMandates(filters: $filters) {
nodes {
id
paymentMethodType
status
statusReason
networkStatus
revocationReason
paymentMethodDetails {
... on PaymentMandateCardDetails {
brand
lastFourDigits
expMonth
expYear
}
... on PaymentMandateBankAccountDetails {
type
lastFourDigits
country
}
}
}
}
}
}
{
"filters": {
"bookingId": { "eq": "1234567890" },
"id": { "in": ["9f1c2b7e-3d4a-4e5f-8a6b-1c2d3e4f5a6b"] }
}
}

Postman examples for both queries:

Values the API doesn’t recognise yet come back as UNKNOWN. Handle that case.

  • BACS collection succeeds — payment.created (status Due), then payment.updated (Success).
  • BACS collection fails for insufficient funds — payment.updated with Failed and INSUFFICIENT_FUNDS. The mandate stays Active, so the payment can be retried.
  • Debit not authorised — payment.updated with Failed and DEBIT_NOT_AUTHORIZED. The bank revokes the mandate, so you also receive payment_method.updated with the mandate Cancelled.
  • Dispute lost — payment.updated with Disputed, then payment.updated with Dispute reversed.
  • Card replaced by the bank — payment_method.updated. Query the mandate for the new last four digits and expiry.
  • SEPA. SEPA mandates get the same webhooks, failure codes and dispute handling. networkStatus, revocationReason, the bank-switch update and expectedDebitDate are BACS only.
  • Testing debit not authorised. In Stripe’s test mode, the BACS debit_not_authorized test accounts fail the payment but don’t deactivate the mandate, because test mode doesn’t send that update. In live, the mandate is cancelled as described above.