Payment sources

Read saved payment sources and use their Wallkit IDs for checkout. For Stripe, create a SetupIntent and save the payment method in separate steps. Neither step charges the member.

Member and resource context

Use an existing member token in the token header and the resource public key in resource. These actions require user access and both contexts. See credentials.

Provider credentials, local customer ownership, resource attachment and live/test selection differ by operation. Check the requirements beside the selected call. A resource header required for initialization does not necessarily filter the returned collection.

Modern source operations

TaskOperation
List the member’s saved sourcesList the member’s saved sources
Create a Stripe customer SetupIntentCreate a Stripe customer SetupIntent
Save the attached Stripe payment methodSave the attached Stripe payment method
Read or create the modern Stripe customerRead or create the modern Stripe customer
Select the local default sourceSelect the local default source
Remove a saved sourceRemove a saved source

Legacy cards and customers

TaskOperation
Add a legacy native cardAdd a legacy native card
List legacy cardsList legacy cards
Select a legacy card defaultSelect a legacy card default
Remove a legacy local cardRemove a legacy local card
Record and gate a card-attachment attemptRecord and gate a card-attachment attempt
Create or import a legacy customerCreate or import a legacy customer
List owned legacy customersList owned legacy customers
Attach a local customer to a resourceAttach a local customer to a resource

Example runtimes, base-address conventions and synthetic values follow response conventions.

List the member’s saved sources

Use this read to choose a source for the selected resource and provider. It performs no provider setup or charge.

GET /api/v1/user/payment-sources

Before you call

Use the member and resource context; apply the source-specific requirements below.

Request

No body.

NameLocationType / requirementMeaning
pay-systemqueryoptional trimmed stringProvider selector. Omission/empty uses resource default_paysystem, falling back to stripe. The validator casts supplied values to integer before a loose comparison with provider names; acceptance is PHP-version-sensitive. Do not rely on it to validate a provider label.

Result

HTTP 200, items array of source summaries. No pagination or general filters. Payment methods come first, then cards without a linked payment method; each group orders saved default first, then descending local ID. At most one summary is marked default: the first saved default encountered.

A default card represented by a method is not duplicated.

No live/test filter is applied to this list; inspect method.mode and use the resource/provider context appropriate for payment.

Listing uses the selected/default provider, while Stripe setup/save use the Stripe operator’s configured mode. With a non-stripe resource default, an unqualified list after saving can omit the newly saved Stripe method; the displayed default describes only the returned collection.

Example: Choose local method 3001

Read the current resource’s sources before checkout. The returned payment-method entry supplies local ID 3001 and test mode; its provider ID is a different value.

curl -X GET "${WALLKIT_API_BASE}/api/v1/user/payment-sources" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-sources`, {
  method: "GET",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-sources",
    method="GET", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"]})
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "items": [
    {
      "source_type": "payment-method",
      "default": true,
      "pay_system": "stripe",
      "customer_id": "cus_SYNTHETIC",
      "user_card": null,
      "payment_method": {
        "id": 3001,
        "payment_method_id": "pm_SYNTHETIC",
        "mode": "test",
        "type": "card",
        "data": {
          "card_brand": "visa",
          "card_country": "US",
          "card_last4": "0000",
          "expiration_date": "2030-12-31"
        }
      }
    }
  ]
}

Consequential alternate

HTTP 200 {"items":[]} means this member has no visible saved sources in the selected resource/provider scope. Start the appropriate configured provider setup path. Visibility follows member-owned customers attached to the resource, not every source held by the provider.

Recovery

StatusCodeCauseNext action
409invalid_pay_systemSelector validation fails.Omit the selector to use configured provider; have the integration administrator check its intended label.
409user_sources_errorLocal source lookup/mapping fails.Keep checkout disabled for that selection and investigate resource/customer associations.

Shared credential failures are described in credentials.

Next task

For source_type payment-method, use payment_method.id (3001 here) as payment_method_id in member calculation, then payment.

For user-card, use user_card.id as user_card_id. Provider IDs are not substitutes.

Create a Stripe customer SetupIntent

Prepare provider setup for an already linked Stripe customer. This creates a provider SetupIntent using off_session usage and automatic payment methods; it does not save a Wallkit payment method, select a default or charge.

POST /api/v1/user/payment-provider/stripe/setup-intents

Before you call

Use the member and resource context; apply the source-specific requirements below.

Require the existing Stripe customer association for this member/resource and configured Stripe private key/account. Use the provider customer ID from the modern Stripe customer result. Provider mode uses resource payments_in_live_mode (default true); no caller mode field is accepted.

Request

JSON object required.

NameLocationType / requirementMeaning
stripe_customer_idJSONrequired nonempty string after trimStripe provider customer ID, such as cus_SYNTHETIC. Lookup restricts it to this member, resource attachment and stripe provider. It is not the numeric Wallkit customer ID.

Result

HTTP 200 setup_intent is a dynamic Stripe SetupIntent object passed through from the configured provider. A nonempty id is checked. Existing consumer code reads client_secret for provider-client confirmation; neither its presence nor the remaining provider properties form a closed Wallkit schema. Preserve the returned object for the existing provider-client flow. This customer-linked SetupIntent path differs from checkout’s direct setup request: it checks the existing local member/resource/customer association before creating provider setup.

Example: Prepare setup for cus_SYNTHETIC

Use the already linked provider customer. The excerpt’s seti_SYNTHETIC ID names a provider SetupIntent; it is not a saved Wallkit source or charge.

curl -X POST "${WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/setup-intents" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "Content-Type: application/json" \
  --data '{"stripe_customer_id":"cus_SYNTHETIC"}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/setup-intents`, {
  method: "POST",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY, "Content-Type": "application/json"},
  body: JSON.stringify({"stripe_customer_id":"cus_SYNTHETIC"})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

body = {'stripe_customer_id': 'cus_SYNTHETIC'}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-provider/stripe/setup-intents",
    method="POST", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"], "Content-Type": "application/json"}, data=json.dumps(body).encode("utf-8"))
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "setup_intent": {
    "id": "seti_SYNTHETIC"
  }
}

Consequential alternate

The excerpt omits all other provider properties, including client_secret. A customer outside the selected association produces HTTP 409 stripe_setup_intent_error, rather than preparing an intent for it.

Recovery

StatusCodeCauseNext action
409incorrect_dataMissing JSON body.Send a JSON object and media header.
409"invalid_customer_id"Missing/empty provider customer ID; quotes are part of code.Read the existing customer result and use its provider customer_id.
409stripe_setup_intent_errorCustomer association, Stripe configuration or provider creation fails.Check member/resource/customer context and configured account; do not assume an intent was created or blindly repeat.

Next task

Complete setup through the existing provider client, then save its attached method. The provider client step is an external prerequisite; this documentation does not execute it.

Save the attached Stripe payment method

Persist a provider payment method for future checkout. For cards this also creates a local card. It clears visible local method/card default flags and marks new records default. It does not charge or update the provider customer’s default payment method.

POST /api/v1/user/payment-provider/stripe/save-intents

Before you call

Use the member and resource context; apply the source-specific requirements below.

Use an attached method obtained from the existing confirmed provider setup flow in the configured resource/account/mode. The action reads only setup_intent.payment_method: it does not retrieve the SetupIntent, verify its ID/status/client secret, or independently prove setup confirmation.

Provider retrieval supplies the method and its customer.

No explicit check proves that provider customer belongs to the authenticated member before it creates/reuses a member-owned local customer association and attaches it to the resource. Trusted integration code must supply the correct member’s method.

Customer/resource association, local card creation and default resets precede the final method save.

Save booleans are unchecked and this path has no enclosing transaction. An error or HTTP success does not establish all writes persisted; read sources before retrying or charging.

Duplicate detection and local method/card default resets use the resource’s default_paysystem (fallback stripe), while the new customer association and saved records use stripe. For this modern Stripe flow, configure the resource default as stripe.

If it selects another provider, these checks inspect that provider’s attached customers: existing Stripe methods can evade duplicate detection, existing Stripe defaults can remain set, and the other provider’s defaults can be cleared. No transaction makes these steps atomic.

Request

JSON object with a nonempty nested object.

NameLocationType / requirementMeaning
setup_intentJSONrequired nonempty objectExisting provider-client setup result. Only the field below is used.
setup_intent.payment_methodJSONrequired nonempty trimmed stringProvider method ID, not Wallkit payment_method_id input for checkout. Provider method must already have a customer.

Result

HTTP 200 payment_method uses the saved method record projection, not the compact source-list DTO. The user_add_payment_method event follows local save attempts.

Example: Save attached method pm_SYNTHETIC

Supply the attached method from the existing provider-client setup result. The returned local ID 3001 is the checkout identifier; attempted default persistence still needs inspection.

curl -X POST "${WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/save-intents" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "Content-Type: application/json" \
  --data '{"setup_intent":{"payment_method":"pm_SYNTHETIC"}}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/save-intents`, {
  method: "POST",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY, "Content-Type": "application/json"},
  body: JSON.stringify({"setup_intent":{"payment_method":"pm_SYNTHETIC"}})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

body = {'setup_intent': {'payment_method': 'pm_SYNTHETIC'}}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-provider/stripe/save-intents",
    method="POST", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"], "Content-Type": "application/json"}, data=json.dumps(body).encode("utf-8"))
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "payment_method": {
    "id": 3001,
    "payment_method_id": "pm_SYNTHETIC",
    "customer_id": "cus_SYNTHETIC",
    "default": true
  }
}

Consequential alternate

This excerpt shows attempted local default selection, not proof of provider default or a charge. Repeating a scoped existing method returns HTTP 409 stripe_payment_method_error with description Payment method already exists. With the resource default set to stripe, read the source list and reuse the saved local ID rather than blindly replaying. A non-stripe default can prevent this duplicate check from finding an existing Stripe method.

Recovery

StatusCodeCauseNext action
409incorrect_dataNo JSON body.Send the JSON object shown.
409"invalid_setup_intent" / invalid_setup_intentMissing / empty nested value.Supply the confirmed provider-client result object.
409"invalid_payment_method_id"Nested method ID absent/empty.Obtain the attached provider method through the existing provider client.
409 payment_method_not_attached_to_customerRetrieved provider method has no customer; code begins with a space.Finish attachment for the correct provider customer before saving.
409stripe_payment_method_errorDuplicate, provider/configuration or local write failure.Read saved sources and inspect member/account association before deciding whether a corrected save is needed.

Next task

Read sources to inspect the saved method, mode and effective list default, then calculate with local payment_method_id 3001. This endpoint is separate from checkout PaymentIntent confirmation.

Saved payment method record

All rows below are selected by save-intents. Stored numeric/flag values have no explicit fetch cast here; callers should handle their serialized representation. Provider-derived optional values can be null. This projection differs from the typed source-list DTO.

FieldType / presenceMeaning
idstored integerLocal method ID for checkout.
payment_method_idstringProvider method ID.
customer_idstringProvider customer ID.
createdstored integerProvider creation value; local source does not convert its unit.
livemode, defaultstored flagsProvider mode flag and local default selection; no explicit output cast.
metadata, billing_details, cardstored JSON text / nullProvider properties stored encoded when objects; this projection does not decode them into JSON objects. Their provider schema is open.
object, typeprovider string / nullProvider object/type labels; no complete enum established.
user_id, payment_customer_id, card_idstored integer / nullLocal member/customer/card references; card_id is null for non-card methods.
created_at, updated_atstringLocal Y-m-d H:i:s timestamps; timezone unspecified.
user_cardobject / nullFor a resolved linked card: id, card_brand, card_country, card_last4, default, expiration_date, created_at; pay_system is conditional on a resolved customer. Numeric ID and default are stored values; remaining fields stored text/null, expiration_date uses Y-m-d. No provider card_id is included here.

Read or create the modern Stripe customer

Obtain the provider customer association needed for modern SetupIntent setup. This GET can create a Stripe customer using the member’s name/email and save local customer/resource associations.

GET /api/v1/user/payment-provider/stripe/customer

Before you call

Use the member and resource context; apply the source-specific requirements below.

Requires configured Stripe account/private key and selected resource mode (payments_in_live_mode, default true). Lookup uses member-owned stripe customers attached to this resource, newest created first. A provider lookup exception is treated as a missing customer: the local customer is deleted and lookup repeats, so temporary provider failure can remove a local association and lead to replacement creation. This path does not establish atomic cleanup of cards/methods/resource relationships. Provider creation precedes unchecked local saves; no enclosing transaction or replay guarantee. Inspect current state before retrying.

Request

No body, query filters or pagination. No caller-selected mode.

Result

HTTP 200 payment_customer is a local customer projection, not a raw provider customer.

FieldType / presenceMeaning
payment_customer.idstored integerWallkit customer record ID; not SetupIntent input.
payment_customer.pay_systemstringstripe for this path.
payment_customer.customer_idstring / nullable annotationProvider customer identifier used as stripe_customer_id for setup.
payment_customer.created_at, updated_atstringLocal Y-m-d H:i:s timestamps; timezone unspecified.
payment_customer.payment_methodsarrayEvery method related to this customer, no pagination/mode filtering or additional per-method resource filter; empty [] allowed. Each uses the saved method record.

Example: Prepare customer 4001 for setup

Read or create the customer in this resource. The result separates local customer 4001 from provider cus_SYNTHETIC; an empty method list means no saved method is shown.

curl -X GET "${WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/customer" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/customer`, {
  method: "GET",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-provider/stripe/customer",
    method="GET", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"]})
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "payment_customer": {
    "id": 4001,
    "pay_system": "stripe",
    "customer_id": "cus_SYNTHETIC",
    "payment_methods": []
  }
}

Consequential alternate

An empty payment_methods array is successful customer preparation, not a saved payment source. Provider retrieval/creation or local failures can return HTTP 409 stripe_customer_error after earlier effects; do not interpret that error as no customer having been created.

Recovery

StatusCodeCauseNext action
409stripe_customer_errorConfigured account, provider or local association step fails.Have the integration administrator inspect selected member/resource/provider state before repeating this GET that can write.

Next task

Use payment_customer.customer_id (cus_SYNTHETIC here) to create a SetupIntent. Customer id 4001 is a different local identifier.

Select the local default source

Select which saved source Wallkit treats as default. This does not charge or change a membership.

PUT /api/v1/user/payment-sources/{source_type}/{id}/default

Before you call

Use the member and resource context; apply the source-specific requirements below.

Choose a source from the member/resource/provider list. Lookup restricts customer ownership and attachment to the resource/provider; no live/test predicate is applied. Provider calls use configured Stripe mode/account.

Local records can be shared through the same customer across resources, so changing/deleting them can affect another resource using that customer.

Only stripe provider dispatch is implemented for these operations; other selected providers fail with Unsupported pay system. Local save/delete booleans are unchecked. Database transaction calls do not guarantee rollback of provider effects.

For payment-method, local method flags reset to the selected ID and card flags reset to its linked card (or all clear when no card). The provider operation retrieves/attaches the method to the customer; it does not update Stripe invoice_settings.default_payment_method.

For user-card, card flags reset and a linked method is selected or all method flags clear; the provider call updates Stripe customer default_source. These branches are different.

Missing source detection follows attempted flag resets; exceptions trigger database rollback, but unchecked save results and provider effects prevent an all-or-nothing guarantee. Empty provider source ID can skip the provider update.

Request

No request body. Use the selected resource/default provider, or optional pay-system query selector with the list validator limitation.

NameLocationType / requirementMeaning
source_typepathrequired stringExactly payment-method or user-card.
idpathrequired local integerpayment_method.id or user_card.id from the source list. Route itself has no digit-only constraint; supply the integer expected by the action. Never substitute provider method/card/customer IDs.
pay-systemqueryoptional stringOmission/empty selects resource default_paysystem, fallback stripe; selector validation is PHP-sensitive.

Result

HTTP 200 success boolean true acknowledges that the path completed. No source projection or provider default confirmation is returned.

Example: Select method 3001 as local default

Select the local payment-method entry from the source list. success acknowledges completion of this branch; read sources afterward to inspect local flags.

curl -X PUT "${WALLKIT_API_BASE}/api/v1/user/payment-sources/payment-method/3001/default" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-sources/payment-method/3001/default`, {
  method: "PUT",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-sources/payment-method/3001/default",
    method="PUT", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"]})
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "success": true
}

Consequential alternate

HTTP 409 user_set_default_payment_method_error can describe Payment method not found or User card not found. The selected source must be visible in this scope. The path can already have attempted local flag changes before detecting that failure.

Recovery

StatusCodeCauseNext action
409unsupported_typeSource type outside the two supported values.Use the returned source_type and its matching local ID.
409invalid_pay_systemSelector validation fails.Use configured provider context; see selector limitation.
409user_set_default_payment_method_errorMissing scoped source, unsupported provider, provider or local failure.Read sources again and inspect provider/account state before deciding on a corrected write.

Next task

Read sources to inspect the effective list default. An explicit local source ID can then be supplied to checkout calculation.

Remove a saved source

Remove a saved method/card and perform its provider detach/delete path. This does not delete the customer or resource attachment, cancel membership or refund transactions.

DELETE /api/v1/user/payment-sources/{source_type}/{id}

Before you call

Use the member and resource context; apply the source-specific requirements below.

Choose a source from the member/resource/provider list. Lookup restricts customer ownership and attachment to the resource/provider; no live/test predicate is applied. Provider calls use configured Stripe mode/account.

Local records can be shared through the same customer across resources, so changing/deleting them can affect another resource using that customer.

Only stripe provider dispatch is implemented for these operations; other selected providers fail with Unsupported pay system. Local save/delete booleans are unchecked. Database transaction calls do not guarantee rollback of provider effects.

Method deletion deletes its linked local card through the card path first, then detaches the provider method, then deletes the local method.

Card deletion attempts its linked method detach/delete before deleting the provider legacy source and local card. A card provider ID beginning pm_ skips legacy source deletion; method detach handles that provider object when its relationship resolves. Missing linked method is tolerated.

No replacement default is selected. The model definitions show no additional deletion hook for these records; customer and resource relationship cleanup is not performed here.

Provider detach/delete can succeed before later local failures or rollback, so an error does not prove the source remains usable.

Request

No request body. Use the selected resource/default provider, or optional pay-system query selector with the list validator limitation.

NameLocationType / requirementMeaning
source_typepathrequired stringExactly payment-method or user-card.
idpathrequired local integerpayment_method.id or user_card.id from the source list. Route itself has no digit-only constraint; supply the integer expected by the action. Never substitute provider method/card/customer IDs.
pay-systemqueryoptional stringOmission/empty selects resource default_paysystem, fallback stripe; selector validation is PHP-sensitive.

Result

HTTP 200 success boolean true; no deleted source/customer projection. Read sources again to inspect local visibility. It is not an erasure or refund acknowledgment.

Example: Remove method 3001

Remove the saved local method and its applicable linked-card/provider associations. The acknowledgment does not delete the customer or select a replacement default.

curl -X DELETE "${WALLKIT_API_BASE}/api/v1/user/payment-sources/payment-method/3001" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-sources/payment-method/3001`, {
  method: "DELETE",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-sources/payment-method/3001",
    method="DELETE", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"]})
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "success": true
}

Consequential alternate

A missing or already removed scoped source returns HTTP 409 user_delete_payment_method_error, not a promised replay-safe success. A partial failure can leave local and provider states different.

Recovery

StatusCodeCauseNext action
409unsupported_typeInvalid path type.Use the source list’s type and matching local ID.
409invalid_pay_systemSelector validation fails.Check configured provider context.
409user_delete_payment_method_errorMissing scoped source, unsupported provider, detach/delete/local failure.Read current sources and ask the integration administrator to inspect provider state before repeating deletion.

Next task

Read sources, choose a remaining usable source and inspect default. If none remain, start the appropriate provider setup path before another checkout.

Legacy card record

FieldType / presenceMeaning
idstored integerLocal card ID for user_card_id in checkout.
card_brand, card_country, card_last4stored text / nullProvider card display properties; not full card credentials.
defaultstored flagLocal default, no explicit response cast.
expiration_datestored string / nullStripe imports format last day of expiry month as Y-m-d; other import paths can leave it unset.
created_atstored stringLocal Y-m-d H:i:s timestamp; timezone unspecified.
pay_systemstring, conditional getCard; selected in card listRelated customer provider label.

No provider card_id or local payment_customer_id is selected. The card list returns these same selected fields. getCard adds pay_system only when its customer resolves.

Legacy customer record

FieldType / presenceMeaning
idstored integerWallkit customer ID used for resource attachment.
pay_systemstored stringCustomer provider label.
customer_idstored string / nullProvider customer identifier, not local ID.
created_at, updated_atstored stringLocal Y-m-d H:i:s timestamps; timezone unspecified.
cardsarrayAll local cards related to this customer, each legacy card; [] allowed. No method projection or per-card resource filter.

Add a legacy native card

Create a provider token/customer and import local cards from native card fields. This legacy action is marked deprecated for test/bot use in source; a new integration should choose its compatible provider-client flow.

POST /api/v1/user/card

Before you call

Use the member and resource context; apply the source-specific requirements below.

Provider selection uses resource default_paysystem fallback stripe and its existing configuration. Native-card token creation is implemented by Stripe; other operators do not establish a universal native-card contract.

Stripe creates token/customer, attempts local customer/resource attachment, imports cards, selects local default and emits add_card.

auto_attach_customer_to_resource defaults true; disabling it can leave created sources absent from resource-scoped reads.

Provider writes precede unchecked local saves; no encompassing transaction, charge or replay guarantee.

The request is a field-layout illustration with an intentionally invalid card-number placeholder. The success excerpt illustrates the configured successful path after appropriate synthetic card input; the literal placeholder instead fails validation. No real card data belongs in these examples.

Request

JSON or form accepted; nonempty form fields take precedence over JSON.

NameLocationType / requirementMeaning
numberbodyrequired string; credit-card validatorNative card number. SYNTHETIC_CARD_NUMBER is an obvious documentation placeholder, not a valid card input.
exp_month, exp_yearbodyrequired; integer sanitizationCard expiry month/year; no handler range constraint.
cvcbodyrequired; integer sanitizationCard security input; no handler range/length guarantee.

Result

HTTP 201 top-level legacy customer record, not a customer wrapper or user profile.

Example: Read native-card field layout

This request uses an intentionally invalid card-number placeholder. The conditional successful-path excerpt shows local card 5001 only after suitable synthetic input; the literal request takes the validation-error branch.

curl -X POST "${WALLKIT_API_BASE}/api/v1/user/card" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "Content-Type: application/json" \
  --data '{"number":"SYNTHETIC_CARD_NUMBER","exp_month":12,"exp_year":2030,"cvc":123}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/card`, {
  method: "POST",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY, "Content-Type": "application/json"},
  body: JSON.stringify({"number":"SYNTHETIC_CARD_NUMBER","exp_month":12,"exp_year":2030,"cvc":123})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

body = {'number': 'SYNTHETIC_CARD_NUMBER', 'exp_month': 12, 'exp_year': 2030, 'cvc': 123}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/card",
    method="POST", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"], "Content-Type": "application/json"}, data=json.dumps(body).encode("utf-8"))
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 201 successful-path excerpt:

{
  "id": 4001,
  "pay_system": "stripe",
  "customer_id": "cus_SYNTHETIC",
  "cards": [
    {
      "id": 5001,
      "card_last4": "0000"
    }
  ]
}

Consequential alternate

HTTP 409 invalid_number: The credit card number is not valid. The literal placeholder demonstrates that invalid-data branch; it cannot create a customer. Provider failure can also follow earlier writes without guaranteeing cleanup.

Recovery

StatusCode / shapeCauseNext action
409invalid_number, invalid_exp_month, invalid_exp_year, invalid_cvcRequired/native input invalid.Correct synthetic field input in the existing configured integration.
406card_failed / add_card_failCreation/import/configuration failure.Inspect existing customer/card/default state before repeating.
500incorrect_actionHistorical body guard (object casting limits its effectiveness).Send explicit supported fields and media.

Next task

List cards to obtain local card ID for checkout; customer creation does not prove entitlement.

List legacy cards

Read local card display data across providers for this resource. Unlike the modern source list, this does not suppress cards linked to payment methods or select one normalized default.

GET /api/v1/user/card

Before you call

Use the member and resource context; apply the source-specific requirements below.

Request

No body. page and limit query values are integers; supply page=1 explicitly. limit defaults to 10. No provider/mode filter is applied. Order is default DESC then created_at DESC. Lookup restricts card user_id and customer attachment resource; it does not add customer-owner or relationship-user predicates.

Result

HTTP 200 items array of legacy card records, plus paginator object: current_page/page integer selected page, total_pages integer page count, total_items integer total results, limit integer selected limit. Duplicated attachment rows can duplicate cards; no DISTINCT is applied.

Example: Read the first page of resource cards

Read ten cards from page 1. The excerpt shows local card 5001 and pagination; its presence does not verify provider usability or entitlement.

curl -X GET "${WALLKIT_API_BASE}/api/v1/user/card?page=1&limit=10" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/card?page=1&limit=10`, {
  method: "GET",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/card?page=1&limit=10",
    method="GET", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"]})
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "items": [
    {
      "id": 5001,
      "card_last4": "0000",
      "pay_system": "stripe",
      "default": true
    }
  ],
  "paginator": {
    "current_page": 1,
    "page": 1,
    "total_pages": 1,
    "total_items": 1,
    "limit": 10
  }
}

Consequential alternate

items can be [] with a paginator; there are no resource-visible local cards. This does not prove the provider has no cards.

Recovery

StatusCode / shapeCauseNext action
Shared auth statusCredential errorsInvalid member/resource context.Restore the selected context.

No dedicated list error code is added by this handler; query/paginator failures use shared handling.

Next task

Use the local card id as user_card_id for member calculation, or select its local default.

Also registered as

GET /api/v1/user/cards calls this same list action and shares the canonical card-list section.

Select a legacy card default

Set local card/default method flags. This path makes no provider default update or charge.

POST /api/v1/user/card/{card_id}/default

Before you call

Use the member and resource context; apply the source-specific requirements below.

Card lookup restricts card user_id, selected resource attachment and local card ID; no provider/mode filter or additional customer-owner/relationship-user predicate. Shared local records may be visible through another resource. PaymentHelper construction still initializes the resource default provider, so provider configuration can fail even when the following changes are local.

Local default cards across attached providers are cleared, then the selected card is marked default.

All local default methods in the resource are cleared; a method linked to this card is marked default if found. The method result is ignored. No transaction; save booleans unchecked, so a successful result does not prove every flag persisted.

Request

Bodyless. card_id path is required digits: local card ID from the card list, not provider card ID.

Result

HTTP 200 result boolean from the local card helper, not the subsequent method helper. false is possible if its second lookup does not resolve.

Example: Select local card 5001

Select a resource-visible legacy card. result reports the card helper, while the subsequent method-flag change has a separate ignored result.

curl -X POST "${WALLKIT_API_BASE}/api/v1/user/card/5001/default" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/card/5001/default`, {
  method: "POST",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/card/5001/default",
    method="POST", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"]})
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "result": true
}

Consequential alternate

HTTP 200 {"result":false} means the local card helper did not select the card. The method helper can still have changed flags; read current lists before another write.

Recovery

StatusCode / shapeCauseNext action
409incorrect_user_card_idInitial scoped card lookup fails.Read resource card list and select its local ID.
Shared failureNo action-specific configuration catchDefault provider initialization fails.Have administrator check resource provider configuration; do not assume flags changed.

Next task

List cards and modern sources to inspect current flags before checkout.

Remove a legacy local card

Delete the local card record. This legacy operation does not detach/delete the provider card or linked payment method, remove customer attachments, cancel memberships or refund.

DELETE /api/v1/user/card/{id}

Before you call

Use the member and resource context; apply the source-specific requirements below.

Card lookup restricts card user_id, selected resource attachment and local card ID; no provider/mode filter or additional customer-owner/relationship-user predicate. Shared local records may be visible through another resource. PaymentHelper construction still initializes the resource default provider, so provider configuration can fail even when the following changes are local.

The helper attempts unchecked local delete; when the deleted card was default it selects a remaining attached card ordered default ASC then customer created_at DESC and marks it default.

There is no provider filter, transaction or guarantee of linked method/default cleanup. The remove_card event receives the pre-deletion card snapshot.

Request

Bodyless. id path is required digits: local card ID.

Result

HTTP 200 result boolean true after the helper path even though delete/save return values are not checked. No card/customer projection.

Example: Remove local card 5001

Delete the legacy local card. result does not acknowledge provider deletion or linked-method cleanup.

curl -X DELETE "${WALLKIT_API_BASE}/api/v1/user/card/5001" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/card/5001`, {
  method: "DELETE",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/card/5001",
    method="DELETE", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"]})
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "result": true
}

Consequential alternate

An unknown or already removed local card returns HTTP 409 incorrect_user_card_id; deletion is not a promised replay-safe success. A linked method can remain after card removal.

Recovery

StatusCode / shapeCauseNext action
409incorrect_user_card_idCard missing from scoped lookup.Refresh card list; do not replace a local ID with a provider ID.
Shared failureNo local provider/configuration catchHelper initialization fails.Inspect configured provider and current local records before retrying.

Next task

Read cards and sources to inspect the remaining default/methods. Use modern removal if that compatible path needs provider detach.

Record and gate a card-attachment attempt

Record an attempt and apply configured locks. It does not tokenize, validate, attach or charge a card. Calling this consumes an attempt; it is not a harmless preflight read.

POST /api/v1/user/card/attempt-attach

Before you call

Use the member and resource context; apply the source-specific requirements below.

A user created within roughly 5 seconds is suspended in this resource and receives user_suspended.

Existing resource membership is needed by the suspension helper; missing relationship failure has no action-specific catch.

Limits use card_attach_attempts_limits (fallback: 3 attempts in 1 minute) and configured card_attach_attempts_locks (fallback levels 1 → 5 minutes, 2 → 30 minutes, 3 → 60 minutes).

Previous attempt level/time/count can deny or escalate; an unknown positive lock level can suspend the relationship.

Events and unchecked attempt/relationship saves occur; success is not proof of provider readiness. The source-specific manual-review helper is not invoked.

Request

Bodyless; no card fields, token or nonce consumed.

Result

HTTP 200 success boolean true after the attempt-record path. No remaining-attempt count, lock-expiry timestamp or source record.

Example: Record one attachment attempt

Record a single attempt in the existing member/resource context. success permits continuation of the existing flow; it supplies no card, token or nonce.

curl -X POST "${WALLKIT_API_BASE}/api/v1/user/card/attempt-attach" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/card/attempt-attach`, {
  method: "POST",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/card/attempt-attach",
    method="POST", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"]})
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "success": true
}

Consequential alternate

HTTP 409 user_suspended can follow an attempted resource suspension. HTTP 409 limit_reached denies another attempt, with a dynamic description; no exact retry timestamp is returned.

Recovery

StatusCode / shapeCauseNext action
409user_suspendedNew-user gate or exhausted configured level.Stop attachment; contact the resource administrator to resolve suspension.
409limit_reachedCurrent attempt window/lock applies.Honor the configured lock and displayed guidance; do not loop retries.
409incorrect_methodMethod check fails.Use POST.

Next task

After success, continue only the existing authorized provider attachment flow; this result alone supplies no saved source ID.

Create or import a legacy customer

Use a provider customer ID or token/nonce to create/import local customer/cards. This returns a member profile, not a single customer envelope.

POST /api/v1/user/customers

Before you call

Use the member and resource context; apply the source-specific requirements below.

Provider token/customer_id is trusted input: the action does not independently prove provider customer ownership before assigning it to the current local member. Existing configuration for the explicit provider is required.

Customer retrieval/creation precedes local customer reuse/create and card import.

auto_attach_customer_to_resource defaults true; attachment failures are logged/swallowed, and false disables attachment.

Local saves unchecked; no enclosing transaction/replay guarantee.

Stripe imports legacy card sources and sets local card defaults; Braintree imports bin-backed local cards without a universal default/method projection. No charge is made.

Request

JSON or form; nonempty form wins.

NameLocationType / requirementMeaning
pay_systembodyrequired exact labelValidation accepts stripe/braintree/paypal. PaymentHelper does not implement paypal, so that accepted label later fails. square is not accepted here.
customer_idbodyconditional nonempty stringProvider customer ID when isset; retrieves/imports existing provider data. Null behaves as absent.
token_idbodyrequired when customer_id not isset; stringProvider token for stripe or nonce for braintree. If both fields are set, customer import runs first then token creation also runs; use one branch to avoid two write sequences.

Result

HTTP 201 resource-aware user, subscriptions and teams from that definition, plus payment_customers array of legacy customer records filtered to member-owned customers attached to this resource and relationship user. No history/tickets/last_action added. Created customer can be absent when resource attachment is disabled/fails.

Example: Import cus_SYNTHETIC for the member

Choose only the existing-customer branch. The profile excerpt shows local customer 4001 in payment_customers; imported cards can still be empty.

curl -X POST "${WALLKIT_API_BASE}/api/v1/user/customers" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "Content-Type: application/json" \
  --data '{"pay_system":"stripe","customer_id":"cus_SYNTHETIC"}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/customers`, {
  method: "POST",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY, "Content-Type": "application/json"},
  body: JSON.stringify({"pay_system":"stripe","customer_id":"cus_SYNTHETIC"})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

body = {'pay_system': 'stripe', 'customer_id': 'cus_SYNTHETIC'}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/customers",
    method="POST", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"], "Content-Type": "application/json"}, data=json.dumps(body).encode("utf-8"))
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 201 response excerpt:

{
  "id": 1001,
  "payment_customers": [
    {
      "id": 4001,
      "pay_system": "stripe",
      "customer_id": "cus_SYNTHETIC",
      "cards": []
    }
  ]
}

Consequential alternate

HTTP 201 payment_customers:[] can occur despite earlier provider/local creation if attachment does not make the customer resource-visible. HTTP 406 incorrect_payment can follow paypal passing validation but unsupported provider initialization.

Recovery

StatusCode / shapeCauseNext action
409invalid_pay_system, invalid_customer_id, invalid_token_idField validation fails.Supply one supported branch and exact provider label.
406incorrect_customerProvider customer retrieval/create failure.Check correct account/member provider input and inspect partial state.
406incorrect_paymentUnsupported/missing provider configuration.Ask administrator to correct provider configuration; paypal has no helper dispatch.
406incorrect_customer_cardCard import fails.Inspect existing customer and imported cards before repeating.
406add_customer_failOther failure.Investigate local/provider partial records before retry.

Next task

List owned customers to get local id, or list cards to choose a local checkout card ID.

List owned legacy customers

Read every local customer owned by the member, across resources/providers. A resource header is required by initialization but does not filter this collection.

GET /api/v1/user/customers

Before you call

Use the member and resource context; apply the source-specific requirements below.

Request

No body, pagination, provider/resource filter or specified order.

Result

HTTP 200 items contains legacy customer records. All related cards are included; no method projection. When no customer exists setItem is never called, so items can be omitted rather than []. Shared response metadata can still appear.

Example: Find local customer 4001

Read the member’s owned customers across resources. Use local id 4001 for authorized resource attachment; provider customer_id is a different identifier.

curl -X GET "${WALLKIT_API_BASE}/api/v1/user/customers" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/customers`, {
  method: "GET",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/customers",
    method="GET", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"]})
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "items": [
    {
      "id": 4001,
      "pay_system": "stripe",
      "customer_id": "cus_SYNTHETIC",
      "cards": []
    }
  ]
}

Consequential alternate

An empty owned-customer result can omit items entirely. Treat absence as no collected customer rows; do not expect a paginator.

Recovery

StatusCode / shapeCauseNext action
Shared auth statusCredential errorsInvalid member/resource context.Restore selected context.

No dedicated list error is caught here.

Next task

Use local customer id for resource attachment, subject to trusted ownership handling described there.

Attach a local customer to a resource

Attempt a local resource/customer relationship insert for the authenticated member. This does not create a provider customer or source.

PUT /api/v1/user/customers/{customer_id}/resources/{resource_key}

Before you call

Use the member and resource context; apply the source-specific requirements below.

The header resource is required, but the destination is the path resource_key. Lookup finds any local customer by ID and any destination resource by public key: no explicit customer-owner, destination membership, account scope or provider/mode check. Trusted integration code must verify the member owns the intended customer and is allowed to attach it to the destination before calling.

An inserted relationship uses current member ID even when customer owner differs; customer-owner filters elsewhere can still hide it.

Existing duplicates are not prechecked. save return false is unchecked, so result true does not prove insertion.

Request

Bodyless.

NameLocationType / requirementMeaning
customer_idpathrequired digitsLocal customer record ID from owned customer list, not provider customer_id.
resource_keypathrequired letters/digits/underscoreDestination resource public key (registered regex [\d\w]+); separate from header context.

Result

HTTP 200 result boolean true after attempted save, no relationship/source projection.

Example: Attach customer 4001 to the intended resource

After trusted ownership/destination checks, attempt the local relationship insert. result true does not independently prove ownership or a persisted save.

curl -X PUT "${WALLKIT_API_BASE}/api/v1/user/customers/4001/resources/RESOURCE_KEY" \
  -H "token: ${USER_TOKEN}" \
  -H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/customers/4001/resources/RESOURCE_KEY`, {
  method: "PUT",
  headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/customers/4001/resources/RESOURCE_KEY",
    method="PUT", headers={"token": os.environ["USER_TOKEN"],
    "resource": os.environ["RESOURCE_KEY"]})
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

Synthetic HTTP 200 response excerpt:

{
  "result": true
}

Consequential alternate

HTTP 406 resource_customer_duplicate describes caught insertion exceptions as already attached, but does not distinguish every possible insert failure. HTTP 200 can still accompany an unchecked false save.

Recovery

StatusCode / shapeCauseNext action
404attach_customer_failEmpty/unknown local customer or destination key.Check owned local customer ID and destination public key.
406resource_customer_duplicateInsert throws; reported as duplicate.Inspect existing authorized relationship and local error before retry.

Next task

List destination cards using that resource as header to inspect visibility. Attachment alone does not establish ownership, saved defaults or a charge.

Full diagram

Use the arrow keys to scroll. Escape closes this view.

Search documentation

Enter at least 2 characters.

    ↑ ↓ move through results · Enter opens · Escape closes