Webhooks Overview

Overview

PaySimple can notify your system or a 3rd party system when an event happens via webhooks. Webhooks are sent with an HTTP post to a publicly accessible url you specify when creating a webhook subscription. You may create as many webhook subscriptions as you wish, and each subscription may subscribe to multiple event types.

Webhook messages are lightweight by design. If you need more context data, call API4 or API5 endpoints using the ids returned in the body.

Delivery

For a webhook message to be delivered successfully, the endpoint must respond with a 2XX status code (eg 200, 201, etc.) within 10 seconds. If a successful response is not received, PaySimple will attempt to resend the webhook once every hour for 72 hours. PaySimple recommends using secure SSL encrypted endpoints hosted on port 443 (https).

Message Consumption Best Practices

It is best to write your consumer in an idempotent manner, as messages may be delivered out of order. Webhook events may be delivered more than once. An event payload will always have a unique event_id, which can be used to prevent duplicates upon message consumption.

Webhook Subscription API

Authorization

All requests to the webhook subscription API must contain the API 5 Authorization header.

Webhook Subscription Object

PropertyTypeDescription
idstringUniquely identifies a webhook subscription
urlstringUrl of the endpoint PaySimple will POST the event message to
event_typesarray of stringsList of event types to be delivered to url
is_activebooleantrue indicates messages should be delivered

Create Webhook Subscription

To create a webhook subscription, call the following endpoint with the url to accept the message and the event types to be sent.

Request:
POST /ps/webhook/subscription
{
    "url" : "https://myserver.mydomain.com/api/webhook",
    "event_types" : [ "payment_created" ],
    "is_active": true
}

Response:
200 OK
{
    "data": {
        "id": "wh_5b45075d253baf7fa0e1b8ee",
        "signature_secret": "3LbX1p2V3w0UDUqh8DWYVVA5tvSosHWWNHutjD3F8nfo3eRzsxbTI97ssGkScqCg"
    }
}

Update Webhook Subscription

Request:
PUT /ps/webhook/subscription/{id}
{
    "url" : "https://myserver.mydomain.com/api/webhook",
    "event_types" : [ "payment_created" ],
    "is_active": true
}

Response:
204 No Content

Get Webhook Subscription

Request:
GET /ps/webhook/subscription/{id}

Response:
200 OK
{
    "data": {
        "id": "wh_5b45075d253baf7fa0e1b8ee",
        "url": "https://myserver.mydomain.com/api/webhook",
        "event_types": [
            "payment_created"
        ],
        "is_active": false,
        "signature_secret": "3LbX1p2V3w0UDUqh8DWYVVA5tvSosHWWNHutjD3F8nfo3eRzsxbTI97ssGkScqCg"
    }
}

Get All Webhook Subscriptions

Request:
GET /ps/webhook/subscriptions

Response:
200 OK
{
    "total_item_count": 4,
    "data": [
        {
            "id": "wh_5be22135d080e311b073d880",
            "url": "https://myserver.mydomain.com/api/webhook",
            "event_types": [
                "payment_created"
            ],
            "is_active": true,
            "signature_secret": "3LbX1p2V3w0UDUqh8DWYVVA5tvSosHWWNHutjD3F8nfo3eRzsxbTI97ssGkScqCg"
        },
        {
            "id": "5be221ead080e311b073d881",
            "url": "https://myserver.mydomain2.com/api/webhook",
            "event_types": [
                "payment_created"
            ],
            "is_active": true,
            "signature_secret": "rY9dneN5dSfj0uH8DCoN5STlbA4abB7OgD12bjN6j7exQ3eH6vUNPVjHEjFQ9y01"
        }
    ]
}

Delete Webhook Subscription

Request:
/ps/webhook/subscription/{id}

Response:
204 No Content

Manually Resend Webhooks

Request:
PUT /ps/webhook/manually_resend_webhooks
{ 
  "ids": ["id_1", "id_2", "id_n"] 
}

Response:
204 No Content

Webhook Verification

Each webhook request sent from PaySimple contains a "paysimple-hmac-sha256" field in the request header, which can be used to verify the message. To verify the message, a webhook recipient can calculate "paysimple-hmac-sha256" using the C# code below. The code requires two parameters: 1) the signature secret and 2) the webhook event request body. The Signature Secret is provided in the response to the request to create the webhook. If you do not have this saved, the signature secret can be obtained by sending a request to the Get Webhook Subscription endpoint. The webhook event request body is is the raw text of the body of the request message that is sent to your webhook consumer.

new HMACSHA256(Encoding.ASCII.GetBytes(signatureSecret))
	.ComputeHash(Encoding.ASCII.GetBytes(webhookEventRequestBody))
	.Aggregate("", (current, t) => current + t.ToString("X2"))

Once you have calculated the "paysimple-hmac-sha256" using the code above, simply compare it to the value of the "paysimple-hmac-sha256" from the webhook request header. If the values match, then the message has been verified.

📘

Troubleshooting Tip

If you consistently fail to verify webhook messages using the code above, be sure to check the webhook event request body for anything that does not match the format of a response as shown above. Occasionally, white space can be added on the consumer end that is different from the original message. In particular check for any white space added in dates before or after a dash ( '-' ) character or any white space before or after special characters.

Webhook Event Types

The following events can be configured for delivery to the url specified in the webhook subscription via an HTTP post.

Webhook Message Object

PropertyTypeDescription
event_typestringName of event
event_idstringUniquely identifies the event.
merchant_idintegerUnique id of PaySimple merchant associated with the event.
created_atdatetimeUTC date and time event was created in ISO-8601 format
dataobjectEvent message body
client_idstring
Deprecated. Use merchant_id. Unique id of PaySimple merchant associated with the event.

Merchant Activated For Payment Type Event Properties

Sent when a merchant is enabled for Credit Card and/or ACH processing for the first time.

This is a reseller scoped webhook. Therefore, when subscribing you should not have the PaySimple-Merchant-Id header set.

PropertyTypeDescription
first_namestringMerchant primary contact first name
last_namestringMerchant primary contact last name
emailstringMerchant primary contact email
company_namestringMerchant name
payment_typestringcredit_card or ach
merchant_keystringPublic identifier for a merchant required by PMT
merchant_idintegerUnique id of merchant. Used in PaySimple-Merchant-Id header for API calls
external_idstringUnique id of merchant in the partner/ISV system as submitted with the merchant application.
processorstringProcessor the merchant has been approved for
{
  "event_type": "merchant_activated_for_payment_type",
  "event_id": "evt_648b5d292171f36bc4d1e240",
  "client_id": 1100234,
  "merchant_id": 1100234,
  "created_at": "2023-06-15T18:49:13.1178895Z",
  "data": {
    "first_name": "Dylan",
    "last_name": "Smith",
    "email": "[email protected]",
    "company_name": "DVSPS 5478932",
    "payment_type": "credit_card",
    "merchant_key": "648b5d288b373814b04828ac",
    "merchant_id": 1100234,
    "external_id": "",
    "processor": "worldpay"
}
{
  "event_type": "merchant_activated_for_payment_type",
  "event_id": "evt_648b40cc2171f36bc4d1dc8b",
  "client_id": 1100233,
  "merchant_id": 1100233,
  "created_at": "2023-06-15T16:48:12.2542378Z",
  "data": {
    "first_name": "Dylan",
    "last_name": "Smith",
    "email": "[email protected]",
    "company_name": "DSSTP 24236",
    "payment_type": "ach",
    "merchant_key": "648b40cb8b373814b04820ff",
    "merchant_id": 1100233,
    "external_id": null,
    "processor": "stripe_ach"
 }

Sale Created From Batch Event Properties

Sent when each individual sale is attempted in a batch sale.

PropertyTypeDescription
external_idstringUnique id of transaction in partner's system

Additional event properties are identical to the sale response.

{
  "event_type": "sale_created_from_batch",
  "event_id": "evt_5b46372e3b63252f94fa2268",
  "merchant_id": 1225,
  "created_at": "2018-07-11T15:44:13.0523594Z",
  "data": {
  {
    "transaction_id": "tx_5d123a99c01d8e0e18599fe2",
    "acquirer_message": "00/Approved",
    "authorization_code": "234243234",
    "batch_id": 4234,
    "card": {
      "card_brand": "mastercard",
      "last4": "0681",
      "expiration_month": 12,
      "expiration_year": 2022
    },
    "avs": {
      "postal_code": "match"
    },
    "outcome": {
      "result": "success",
      "code": "1000",
      "description": "Approved"
    },
    "external_id": "2323423"
  }
}

Transaction Events

transaction_settled

Sent when a transaction has been settled (funds transferred).
Note: a negative settlement_amount indicates a refund has settled.

PropertyTypeDescription
transaction_idstringThe id of the transaction returned upon creation
transaction_amountdecimalThe amount that was authorized by the transaction
transaction_external_idstringThe external_id field that was provided when the transaction was created
transaction_descriptionstringThe description field that was provided when the transaction was created
transaction_datedatetimeThe date and time the transaction was created
settlement_codestringThe funding code provided by the processor.
settlement_amountdecimalThe amount that was actually funded for the transaction. This will typically be the same as transaction_amount
settlement_datedatetimeThe date and time the transaction was funded
transaction_feesarray of transaction-fee objects (see below)Array of transaction-fees associated with the transaction. If transaction-fees are inapplicable or unavailable (e.g., because the processor does not report transaction-fees), this element is not present.
-- namestringName of fee.
--notesstringAdditional information about the fee. May not be present.
--amountdecimalFee-amount. a negative value indicates that the merchant owes this amount.
transaction_fees_amountdecimalSum of fees associated with the transaction. If transaction-fees are inapplicable or unavailable, this element is not present.
transaction_net_amountdecimalThe net-total amount deposited.
{
  "event_type": "transaction_settled",
  "event_id": "evt_6485266d4e2250acd76ea55f",
  "client_id": 301477,
  "merchant_id": 301477,
  "created_at": "2023-06-11T01:42:05.5891529Z",
  "data": {
    "transaction_id": "tx_64838060f47f63960dba09b6",
    "transaction_amount": 15.01,
    "transaction_external_id": "fc1c673a-6735-4919-ad79-19d9d8407818",
    "transaction_fees": [
      {
        "name": "PaySimple application fee",
        "notes": "application_fee",
        "amount": -0.45
      }
    ],
    "transaction_fees_amount": -0.45,
    "transaction_date": "2023-06-09T19:41:21.621795Z",
    "transaction_net_amount": 14.56,
    "settlement_amount": 15.01,
    "settlement_date": "2023-06-11T01:42:03Z"
  }
}

Note the following:

  • The event has both client_id and merchant_id properties (not just merchant_id).
  • The event does not have a settlement_date property (it is part of event.data).

transaction_returned

Sent when a transaction is unsuccessful (funds not transferred)

PropertyTypeDescription
transaction_idstringThe id of the transaction returned upon creation
transaction_amountdecimalThe amount that was authorized by the transaction
transaction_external_idstringThe external_id field that was provided when the transaction was created
transaction_descriptionstringThe description field that was provided when the transaction was created
transaction_datedatetimeThe date and time the transaction was created
settlement_codestringFor ACH this will be the NACHA return code that indicates why the transaction was returned and not funded. See a full list here under the Transaction Return Codes section.
settlement_amountdecimalThe amount that was actually funded for the transaction. This will typically be the same as transaction_amount
settlement_datedatetimeThe date and time the transaction was returned
transaction_feesarray of transaction-fee objects (see below)Array of transaction-fees associated with the transaction. If transaction-fees are inapplicable or unavailable (e.g., because the processor does not report transaction-fees), this element is not present.
-- namestringName of fee.
-- notesstringAdditional information about the fee. May not be present.
-- amountdecimalFee-amount. a negative value indicates that the merchant owes this amount.
transaction_fees_amountdecimalSum of fees associated with the transaction. If transaction-fees are inapplicable or unavailable, this element is not present.
transaction_net_amountdecimalThe net-total amount deposited.
{
    "event_type": "transaction_returned",
    "event_id": "evt_62daff92ad0d1c43049defb7",
    "merchant_id": 1225,
    "settlement_date": "2023-06-06T01:44:28Z",
    "data": {
        "transaction_id": "tx_647e51ec057a9518f6da6ff5",
        "transaction_amount": 13.20,
        "transaction_external_id": "a14e59ac-1a66-4a4b-9aa6-c6a267a6d5cf",
        "transaction_fees": [
            {
                "name": "PaySimple application fee",
                "notes": "application_fee",
                "amount": -0.43
            }
        ],
        "transaction_fees_amount": -0.43,
        "transaction_date": "2023-06-05T21:21:50.220706Z",
        "transaction_net_amount": -13.63,
        "settlement_code": "R02",
        "settlement_amount": -13.20,
        "settlement_date": "2023-06-06T01:44:28Z",
        "settlement_description": "Account closed"
    }
}

Other Webhook Events

Account Updater - Status Changed
Account Updater - Card Updated
Applications - Application Status Updated
Payment, customer and offering events are not available for payments processed via the Partner/ISV Payments API

Testing Webhooks

To test in the PaySimple sandbox environment, you can send yourself a dummy webhook with static message data by calling the following endpoint:

Request:
POST /ps/webhook/subscription/test
{
    "url" : "https://myserver.ngrok.io/api/webhook",
    "event_type" : "merchant_activated_for_payment_type"
}

Response:
200 OK
{
    "data": {
        "status_code": "OK"
    }
}

This particular example of event type requires Reseller Authorization header without a merchant-id. However all event types can be tested using either the Reseller or Merchant authorization referenced here API 5 Authorization header.
The endpoint will return the HTTP status code in the body that was returned by your endpoint when we attempted to connect to it.

For testing webhooks on a development or other non-publicly accessible machine, we recommend using ngrok to tunnel webhook traffic to you.