# Troubleshooting & FAQ

## Status issues

### Why does my link say expired, deactivated, or completed?

A payment link transitions to a terminal or inactive status when one of the following conditions is met:

| Status  | Cause |
|  --- | --- |
| `expired` | The current time has passed `restrictions.dates.expiresAt`. This happens automatically — no action is needed. |
| `deactivated` | The link was manually disabled, either via the Customer Hub or by sending `POST /paymentlinks/update` with `paymentLink.status: "deactivated"`. |
| `completed` | The number of successful payments has reached `restrictions.payments.limit`. |


To inspect the cause, retrieve the link (`PaymentLinkId` in the header, not the URL) and check the `status`, `restrictions.dates.expiresAt`, and `restrictions.payments.count` fields:

```bash
curl https://api.shift4.com/api/rest/v1/paymentlinks/retrieve \
  -H "AccessToken: YOUR_ACCESS_TOKEN" \
  -H "InterfaceVersion: 1.0" \
  -H "InterfaceName: YourApp" \
  -H "CompanyName: YourCompany" \
  -H "PaymentLinkId: link_8kdskX8DZ8FR6W3acYPXiyAN"
```

If the link expired earlier than expected, check the format of `restrictions.dates.expiresAt`. The field expects an ISO 8601 timestamp (for example, `2026-05-27T12:00:00.000Z`) — sending a Unix epoch value will either be rejected or interpreted as a date in the distant past, causing the link to expire immediately.

### Why can't I reactivate my link?

Only `deactivated` links can be reactivated. Links in the `expired` or `completed` status are permanently closed and cannot be reopened.

| Status | Can reactivate? |
|  --- | --- |
| `deactivated` | Yes — `POST /paymentlinks/update` with `paymentLink.status: "active"`, or use the Customer Hub |
| `expired` | No — create a new link |
| `completed` | No — create a new link |
| `scheduled` | No action needed — it activates automatically at `activatesAt` |


If a link expired before payment was received and you need to accept payment, create a new link with the same configuration. There is no way to extend the expiry date of an existing link.

### What happens when a customer opens a link that is no longer active?

The customer sees a Shift4-hosted page indicating that the link is unavailable. The page describes the reason (expired, deactivated, or sold out) but does not expose internal configuration. The customer cannot complete a payment.

Payment Links list
### My customer paid but the link is still active — why?

This is expected when `restrictions.payments.limit` is greater than 1 (or omitted entirely — the default is 1). The link stays `active` until `restrictions.payments.count` reaches the configured limit. To confirm whether payment went through, retrieve the link and check `restrictions.payments.count`, or list the underlying checkout sessions with `GET /checkoutsessions/list` filtered by the `PaymentLinkId` header.

## Notifications

### Why didn't my email notification send?

Email notifications require a `customer` on the create request. The customer can be referenced by `id` (an existing Shift4 customer record) **or** supplied inline — but an email address must be present:

**Email** (`paymentLink.notifications.share.email: true`):

- Either `customer.id` of an existing record that has `emailAddress` set, or an inline `customer.emailAddress` on the create request.
- Without an email address available, the notification field is silently ignored.


To confirm whether a notification was sent, retrieve the payment link and inspect `paymentLink.notifications.share.email.status` in the response. Values are `sent`, `delivered`, or `failed`.

### Can I re-send a notification after the link is created?

There is no resend endpoint. Notifications fire once at link creation (when `paymentLink.notifications.share.email` is `true`) and cannot be re-triggered via the API. If a customer didn't receive their notification, the options are:

- **Same link, manual share** — retrieve the link and re-send the `paymentLink.url` over any channel (your own email, chat, etc.).
- **New link** — create a fresh payment link for the same amount and let Shift4 send the notification again. Deactivate the original first so there aren't two active payment paths for the same transaction.


## Payment methods

### I enabled Apple Pay / Google Pay but the buttons don't appear at checkout

Even with the digital wallet toggles switched on in **Settings → Payment Links**, the Apple Pay and Google Pay buttons only render at checkout if your business is eligible to accept them. Apple and Google each maintain their own list of prohibited and restricted business categories — if your merchant category code (MCC) falls into one of these, the wallets are suppressed and no button is shown, regardless of the Customer Hub setting.

Common ineligible categories include tobacco and vaping, firearms and weapons, adult content, cryptocurrency exchanges, certain gambling, and other regulated verticals. The authoritative lists are maintained by Apple and Google directly:

- [Apple Pay on the Web — Acceptable Use Guidelines](https://developer.apple.com/apple-pay/acceptable-use-guidelines-for-websites/)
- [Google Pay APIs — Acceptable Use Policy](https://payments.developers.google.com/terms/aup)


If your business model is permitted under both policies and the buttons still don't appear, contact your Shift4 representative to confirm digital wallets are enabled for your MCC.

## Payment confirmation

### Can I trust the redirect to returnUrl as payment confirmation?

The redirect to `returnUrl` fires after a successful checkout, but it must not be treated as authoritative payment confirmation — a malicious actor could navigate directly to your return URL. Always confirm payment server-side:

1. Read the `checkout_session_id` query parameter from the redirect URL.
2. Retrieve the Checkout Session (the session ID goes in the `CheckoutSessionId` header):

```bash
curl https://api.shift4.com/api/rest/v1/checkoutsessions/retrieve \
  -H "AccessToken: YOUR_ACCESS_TOKEN" \
  -H "InterfaceVersion: 1.0" \
  -H "InterfaceName: YourApp" \
  -H "CompanyName: YourCompany" \
  -H "CheckoutSessionId: chse_wMCUGVZgpf45fWHzKbYWMKmZ"
```
3. Inspect `checkoutSession.lastTransaction` on the response — it is the underlying sale-response object (card brand, last four, captured amount, success/failure). Treat a successful `lastTransaction` as authoritative confirmation.


For a fully server-side confirmation that doesn't depend on the browser redirect at all, subscribe to the `checkout_session_completed` webhook event. This fires regardless of whether the customer's browser ever reaches your return URL.

## Data and reconciliation

### Where do I find the values customers entered in custom fields?

Custom field answers are stored on the **Checkout Session**, not on the payment link. To retrieve them:

1. Get the Checkout Session ID from one of these sources:
  - The `checkout_session_id` query parameter in your `returnUrl` redirect.
  - The `checkoutSession.id` field of the `checkout_session_completed` webhook event.
  - A session returned from `GET /checkoutsessions/list` with the `PaymentLinkId` header set to your link's ID.
2. Retrieve the session via `GET /checkoutsessions/retrieve` with the session ID in the `CheckoutSessionId` header.
3. Read the `customFields` array on the response. Each entry contains the `key` and `label` you defined at link creation plus the `value` the customer entered.


Custom fields are not visible in the Customer Hub's payment link detail view — you must access them via the API or webhook payload.

### How do I reconcile a payment back to an invoice or order in my system?

Three fields are available for reconciliation, each with a different purpose:

| Field  | Visible to customer | Where it lives | Max length | Use for |
|  --- | --- | --- | --- | --- |
| `metadata` | No | Top level of the create request, alongside `paymentLink` | Key/value strings | Internal IDs, CRM keys, ERP references |
| `paymentLink.vendorReference` | No | Payment link object | 50 characters | External invoice or order number; reportable in the Merchant Portal and settlement extract |
| `paymentLink.staticFields` | Yes | Payment link object (key/value object) | Title key max 55 chars, value max 255 chars | Context shown to the customer alongside the amount |


The recommended pattern for A/R reconciliation:

- Set `paymentLink.vendorReference` to your invoice number.
- Set top-level `metadata` with any additional internal IDs (customer ID, ERP record, case number).
- Listen for `checkout_session_completed` webhooks. The webhook payload includes `paymentLink.id` (not the full link) and the `checkoutSession` — including its `staticFields` and `customFields`. Use `paymentLink.id` to retrieve the link and read `vendorReference`/`metadata` if you need them; or rely on `staticFields` / `customFields` already on the session.


Avoid encoding reconciliation data only in the product `name` — that field is customer-facing and has no guaranteed format.

### Can I modify a payment link after creating it?

`POST /paymentlinks/update` accepts most of the same fields as create — line items, `restrictions` (dates and `payments.limit`), `returnUrl`, `collectBillingAddress`/`collectShippingAddress`, `notifications`, `staticFields`, `customFields`/`customFieldsTitle`, `locale`, and `status` (used to deactivate/reactivate).

That said, if a payment has already been processed against a link, changing the amount or product is rarely the right move — it creates a mismatch between historical transactions and the current configuration. The safer pattern when correcting an error:

1. Deactivate the original link (`POST /paymentlinks/update` with `paymentLink.status: "deactivated"`) to prevent further payments.
2. Create a new link with the corrected configuration.
3. Communicate the new URL to the customer.


### Can the same customer pay multiple times with the same link?

Yes, if `restrictions.payments.limit` is greater than 1. Each successful checkout creates a separate Checkout Session. The link transitions to `completed` once the limit is reached.

For single-use links (for example, a link sent to one specific customer), set `restrictions.payments.limit: 1` — which is also the default if you omit `restrictions.payments`. This guarantees the link accepts exactly one successful payment and then closes automatically.

## Webhooks

### My webhook endpoint is not receiving events — how do I debug this?

Work through the following checklist:

1. **Endpoint returns 200.** Shift4 considers any non-`200` response a delivery failure. Return `200` immediately upon receipt, before any processing logic that might fail.
2. **Endpoint is reachable from the internet.** Localhost or internal network addresses will not receive webhooks in production. Use a tunneling tool (ngrok, Cloudflare Tunnel) for local development.
3. **Notification URL is correctly configured.** The notification URL is set during merchant boarding (not via the API). If yours is wrong or missing, contact your Shift4 representative — there is no self-serve endpoint to update it.
4. **Confirm the event you expect is enabled.** Only `paymentlinks-notification` (`payment_link_created`, `payment_link_updated`) and `checkoutsessions-notification` (`checkout_session_completed`) fire for Payment Links — see [Monitor and reconcile → Notifications](/guides/core-concepts/payment-links/guides/monitor-and-reconcile/api#notifications).


For payload format and the full list of event types, see [Monitor and reconcile](/guides/core-concepts/payment-links/guides/monitor-and-reconcile).