> For the complete documentation index, see [llms.txt](https://docs.solapayments.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.solapayments.com/products/webhooks.md).

# Webhooks

Sola account webhooks notify your application in real time each time a transaction is processed on your account. Sola sends each notification as an HTTP `POST` request to a callback URL that you configure in the Sola Portal.

Use webhooks to keep your system in sync with transaction activity without polling the API.

### How webhooks work <a href="#how-webhooks-work" id="how-webhooks-work"></a>

1. A transaction is processed on your Sola account.
2. Sola sends a `POST` request to your callback URL.
3. Your endpoint verifies the `ck-signature` header, if a Webhook PIN is configured.
4. Your endpoint stores or queues the notification and returns a success response.
5. Your application processes the notification.

Sola queues delivery asynchronously and waits up to **10 seconds** for your endpoint to respond.

| Your response                                      | Result                                                                            |
| -------------------------------------------------- | --------------------------------------------------------------------------------- |
| Any `2xx` status (`200`–`299`)                     | Delivery succeeded. A response body is not required, so `204 No Content` is fine. |
| Any other status, or no response within 10 seconds | Delivery failed.                                                                  |

Each notification is delivered as a single attempt, and delivery order is not guaranteed. Design your endpoint to respond quickly and to handle notifications that arrive out of order. See [Handle notifications](#handle-notifications).

### Before you begin <a href="#before-you-begin" id="before-you-begin"></a>

You need:

* A Sola account with access to the [Sola Portal](https://portal.solapayments.com/).
* An **Admin** or **Advanced User** role, which is required to view and change the webhook configuration.
* A publicly accessible HTTPS callback URL with a valid TLS certificate that accepts `POST` requests.

Your callback URL must be reachable from the public internet. A `localhost` address or an endpoint restricted to a private network does not work.

### Set up a webhook <a href="#set-up-a-webhook" id="set-up-a-webhook"></a>

Each Sola account supports one webhook configuration. Configure sandbox and production accounts separately.

1. Sign in to the Sola Portal.
2. Go to **Settings > Gateway Settings > Webhook**.
3. In **Postback URL**, enter your HTTPS callback endpoint.
4. Optionally, in **PIN**, enter a Webhook PIN. When a PIN is set, Sola signs each notification so your application can verify that it came from Sola.
5. Select the fields to include in the payload. A selected field is sent whenever it applies to the transaction.
6. Select **Save Changes**.

To change the webhook, update the Postback URL, PIN, or field selections and select **Save Changes**.

To turn the webhook off, clear the **Postback URL** field and select **Save Changes**.

To manage the configuration programmatically, see the [Sola Settings API reference](https://cardknox.gitbook.io/sola-dev-docs/api-reference/setting-api/cardknox-settings).

#### Webhook PIN requirements <a href="#webhook-pin-requirements" id="webhook-pin-requirements"></a>

The Webhook PIN is optional. If you set one, it must:

* Contain only letters and numbers.
* Be at least 15 characters long.

Sola includes the `ck-signature` header only when a PIN is configured. If you do not set a PIN, notifications are delivered without a signature and your application cannot verify their origin.

Generate a long, unpredictable value and store it in a secret manager or a protected server-side environment variable. Never place it in source code, client-side code, or logs.

### Events that trigger a webhook <a href="#events-that-trigger-a-webhook" id="events-that-trigger-a-webhook"></a>

Sola sends a webhook for each transaction that reaches the transaction-response stage, including approved, declined, and errored transactions.

| Command family    | Commands                                                                                                                                                                                                                                                                                             |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Credit card       | `cc:sale`, `cc:authonly`, `cc:capture`, `cc:splitcapture`, `cc:postauth`, `cc:void`, `cc:voidrelease`, `cc:voidrefund`, `cc:creditvoid`, `cc:refund`, `cc:credit`, `cc:balance`, `cc:uncapture`, `cc:avsonly`, `cc:avsprecheck`, `cc:save`, `cc:adjust`, `cc:unenrollaccountupdater`, `cc:authclose` |
| ACH/check         | `check:sale`, `check:credit`, `check:void`, `check:refund`, `check:voidrefund`, `check:adjust`, `check:save`                                                                                                                                                                                         |
| EBT food benefits | `ebtfs:sale`, `ebtfs:authonly`, `ebtfs:return`, `ebtfs:credit`, `ebtfs:voucher`, `ebtfs:balance`, `ebtfs:void`                                                                                                                                                                                       |
| EBT cash benefits | `ebtcb:sale`, `ebtcb:authonly`, `ebtcb:cash`, `ebtcb:balance`, `ebtcb:void`                                                                                                                                                                                                                          |
| eWIC              | `ebtw:sale`, `ebtw:authonly`, `ebtw:capture`, `ebtw:void`,`ebtw:credit`, `ebtw:voucher`, `ebtw:balance`                                                                                                                                                                                              |
| EBT Online        | `ebtonline:initiate`, `ebtonline:balance`, `ebtonline:fssale`, `ebtonline:cbsale`, `ebtonline:refund`, `ebtonline:void`                                                                                                                                                                              |
| Gift              | `gift:balance`, `gift:issue`, `gift:redeem`, `gift:updatecard`, `gift:addcard`, `gift:enroll`, `gift:expire`, `gift:activate`, `gift:deactivate`                                                                                                                                                     |
| Fraud             | `fraud`, `fraud:submit`, `fraud:resubmit`, `fraud:adjust`, `fraud:save`                                                                                                                                                                                                                              |
| Grant             | `grant:recommendation`, `grant:void`, `grant:refund`                                                                                                                                                                                                                                                 |
| Other             | `trn:signature`                                                                                                                                                                                                                                                                                      |

For recurring payments, each initial, scheduled, retry, and backup-payment-method attempt is a separte transaction and generates its own notification.

#### Actions that do not trigger a webhook <a href="#actions-that-do-not-trigger-a-webhook" id="actions-that-do-not-trigger-a-webhook"></a>

Actions that do not process a payment transaction do not generate a notification. For example:

* Running reports or queries
* Closing or settling of a batch
* Changing a recurring schedule
* Updating account, customer, or stored payment-method information

### Request format <a href="#request-format" id="request-format"></a>

#### Headers and body <a href="#headers-and-body" id="headers-and-body"></a>

| Property             | Value                                                                                                         |
| -------------------- | ------------------------------------------------------------------------------------------------------------- |
| Method               | `POST`                                                                                                        |
| Content-Type         | `application/x-www-form-urlencoded; charset=utf-8`                                                            |
| Body                 | URL-encoded key-value pairs                                                                                   |
| Signature header     | `ck-signature`, sent when a Webhook PIN is configured                                                         |
| Authorization header | Not used. Webhooks are outbound notifications, so your application does not send credentials to receive them. |

Standard transport headers such as `Content-Length`, `Accept-Encoding`, and `Expect` may also be present. Do not rely on them for authentication or business logic.

#### Encoding <a href="#encoding" id="encoding"></a>

* The body uses standard URL form encoding.
* Spaces are encoded as `+`. Other characters may be percent-encoded.
* Text is encoded as UTF-8.
* HTTP header names are case-insensitive.
* Treat parameter names as case-insensitive when parsing, but preserve their decoded case when generating the signature.

#### Example payload <a href="#example-payload" id="example-payload"></a>

The following example shows the payload format using fictional values. Do not use it as a signature test vector.

```
POST /your-callback HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=utf-8
ck-signature: 0123456789ABCDEF0123456789ABCDEF

xAmount=12.34&xCommand=cc%3Asale&xEnteredDate=01%2F02%2F2030+03%3A04%3A05+PM&xInvoice=DEMO-INV-0001&xMaskedCardNumber=XXXXXXXXXXXX0000&xName=Example+Person&xOrderID=DEMO-ORDER-0001&xRefNum=9999999999&xResponseResult=Approved
```

Decoded:

| Parameter           | Value                    |
| ------------------- | ------------------------ |
| `xAmount`           | `12.34`                  |
| `xCommand`          | `cc:sale`                |
| `xEnteredDate`      | `01/02/2030 03:04:05 PM` |
| `xInvoice`          | `DEMO-INV-0001`          |
| `xMaskedCardNumber` | `XXXXXXXXXXXX0000`       |
| `xName`             | `Example Person`         |
| `xOrderID`          | `DEMO-ORDER-0001`        |
| `xRefNum`           | `9999999999`             |
| `xResponseResult`   | `Approved`               |

### Payload contents <a href="#payload-contents" id="payload-contents"></a>

The payload does not have a single fixed set of fields. The fields you receive depend on:

* The fields you selected in the Portal
* The transaction type and payment method
* The Sola product or integration that originated the transaction
* Whether a value applies to that specific transaction

Sola may also include system-generated parameters that you did not select individually, and may introduce new parameters over time. Each notification contains at most one value per parameter name.

**Build your parser defensively.** Read the body as an open collection of key-value pairs, use only the fields your integration needs, and ignore unknown parameters rather than failing on them. Do not require every possible field to be present.

When verifying the signature, use every received parameter. Never calculate the signature from a fixed allowlist of parameter names.

#### Verify the `ck-signature` <a href="#verify-the-ck-signature" id="verify-the-ck-signature"></a>

For security purposes and to prevent forged notifications, a PIN can be set up when configuring the webhook. When a PIN is configured, Sola includes a hash of the payload in the `ck-signature` header. Verify this value before you process a notification. If your hash matches the `ck-signature`, the webhook originated from Sola.

`ck-signature` is a 32-character uppercase hexadecimal MD5 digest. HTTP header names are case-insensitive, so read the header without assuming a particular casing.

**Verification steps**

1. **Parse the body.** Parse the body as UTF-8 `application/x-www-form-urlencoded`. Convert `+` to a space and percent-decode names and values.
2. **Normalize key case.** Convert all parameter names to lowercase, for case-insensitive sorting.
3. **Sort alphabetically.** Sort the parameters alphabetically by name.
4. **Extract the values.** Take the value of each parameter in that order. Do not include names or separators. Use each value exactly as decoded — do not trim, pad, or reformat it.
5. **Append the PIN.** Concatenate the ordered values, then append the Webhook PIN to the end.
6. **Replace non-ASCII characters.** Replace each non-ASCII character in the concatenated string with an ASCII question mark (`?`). For example, `Café` becomes `Caf?`.
7. **Generate the hash.** Calculate the MD5 hash of the resulting ASCII string and format it as uppercase hexadecimal.
8. **Compare.** Normalize your digest and the `ck-signature` value to the same letter case, then compare them using a constant-time comparison function if your platform provides one. Reject the notification if they do not match, or if the `ck-signature` header is missing or empty.

In summary:

`MD5(replace_non_ASCII_with_?(sorted_value_1 + ... + sorted_value_n + webhook_pin))`

**Note on MD5:** MD5 is used here for compatibility with Sola's verification protocol. Do not use MD5 for password storage or for new security designs.

### Handle notifications <a href="#handle-notifications" id="handle-notifications"></a>

Process each notification in this order:

```
Receive the POST request
        |
Parse the URL-encoded form data
        |
Verify ck-signature, if configured
        |
Check whether you already received this transaction
        |
Store or queue the notification
        |
Return a 2xx response
        |
Process the notification asynchronously
```

#### Respond quickly <a href="#respond-quickly" id="respond-quickly"></a>

Return a `2xx` response within 10 seconds, then do slower business processing asynchronously. Because each notification is delivered as a single attempt, an endpoint that times out or errors loses that notification. Keep the request handler thin: verify, persist, respond.

#### Handle duplicates and out-of-order delivery <a href="#handle-duplicates-and-out-of-order-delivery" id="handle-duplicates-and-out-of-order-delivery"></a>

Notifications may arrive out of order, and in rare cases the same notification may be delivered more than once. A duplicate notification does not mean the transaction was processed twice.

Process notifications idempotently so a repeat delivery does not trigger a duplicate business action. Use the transaction identifiers in the payload, such as `xRefNum`, to detect a notification you have already handled.

### Test your integration <a href="#test-your-integration" id="test-your-integration"></a>

Configure a webhook on a sandbox account and run test transactions against it.

Before you enable the webhook in production, confirm that:

* The callback endpoint is publicly reachable over HTTPS.
* The endpoint correctly parses URL-encoded form data.
* If you configured a PIN, a notification with a valid `ck-signature` passes verification, and a notification with an invalid or missing signature is rejected.
* The endpoint responds well within the 10-second window.
* A duplicate notification does not process a transaction twice.
* Secrets and unnecessary payment data do not appear in your logs.

Use separate callback endpoints and separate Webhook PINs for sandbox and production.

### Troubleshooting <a href="#troubleshooting" id="troubleshooting"></a>

#### The endpoint receives no notifications <a href="#the-endpoint-receives-no-notifications" id="the-endpoint-receives-no-notifications"></a>

Confirm that:

* The callback URL is saved in the webhook configuration for the correct account.
* The URL is publicly accessible over HTTPS and accepts `POST` requests.
* The transaction you ran uses a command that generates a webhook. See [Events that trigger a webhook](#events-that-trigger-a-webhook).
* Your firewall, proxy, or WAF is not blocking the request.

#### Signature verification fails <a href="#signature-verification-fails" id="signature-verification-fails"></a>

Work through the [verification steps](#verify-the-ck-signature) and confirm that:

* You are using the PIN currently saved for this webhook.
* The body was parsed as UTF-8 form data, including `+`-to-space conversion and percent-decoding
* Only the values were concatenated, with no names or separators.
* The PIN was appended after all values.
* Each non-ASCII character was replaced with `?` before hashing.
* No value was trimmed or reformatted.
* Your digest is 32 hexadecimal characters and is compared case-insensitively.

#### A notification is processed more than once <a href="#a-notification-is-processed-more-than-once" id="a-notification-is-processed-more-than-once"></a>

Deduplicate using the transaction identifiers in the payload, such as `xRefNum`, and make your processing idempotent. See [Handle duplicates and out-of-order delivery](#handle-duplicates-and-out-of-order-delivery).

#### A notification arrives with unexpected or missing fields <a href="#a-notification-arrives-with-unexpected-or-missing-fields" id="a-notification-arrives-with-unexpected-or-missing-fields"></a>

This is expected behavior. Field availability varies by transaction, and Sola omits parameters that the value was zero or false. See [Payload contents](#payload-contents).

#### The transaction result is unclear <a href="#the-transaction-result-is-unclear" id="the-transaction-result-is-unclear"></a>

See [Response Parameters](https://docs.solapayments.com/#response-parameters) for transaction outcomes and response-result meanings.

### Notes on current support <a href="#notes-on-current-support" id="notes-on-current-support"></a>

The following are not available today. Where relevant, the recommended approach is noted.

| Item                                                                       | Status                  | Recommended approach                                                                                                                             |
| -------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Automatic retry of failed deliveries                                       | Not currently supported | Keep the request handler fast and return `2xx` before doing business processing. Reconcile against the API if you suspect a missed notification. |
| Delivery logs and manual replay in the Portal                              | Not currently supported | Log every notification you receive, including rejected ones.                                                                                     |
| Filtering delivery by event or transaction type                            | Not currently supported | Field selection controls payload contents, not which transactions notify. Filter on `xCommand` in your handler.                                  |
| Test-event generator                                                       | Not currently supported | Test by running transactions on a sandbox account.                                                                                               |
| Dedicated webhook idempotency key                                          | Not currently supported | Deduplicate using transaction identifiers such as `xRefNum`.                                                                                     |
| Payload version field, deprecation policy, and advance change notification | Not currently supported | Parse the payload defensively and ignore unknown parameters.                                                                                     |
| Multiple webhook configurations per account                                | Not currently supported | Each account supports one configuration. Use one endpoint and fan out on your side.                                                              |

### Support <a href="#support" id="support"></a>

For help with webhook configuration or delivery, visit [Sola Support](https://solapayments.com/support/).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.solapayments.com/products/webhooks.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
