WebHooks

It is a good practice to verify the state of a transaction independently from your app such as via your backend. Using WebHooks to verify transactions enables notifications about transaction events.
Register a WebHook
  1. You can edit WebHooks in the Gateway Manager.
  2. Go to the account settings by clicking your email in the top right corner.
  3. In the WebHooks section, you can create, edit, and delete WebHooks.
After the WebHook is set up, you will receive event callbacks when the
status
of your transaction changes.
Handle Incoming Event Data
For an
APPROVED
transaction, a
transaction.succeeded
event is fired both for
CHARGE
and
REFUND
transactions. This is what the body of the
HTTP POST
request to your registered WebHook URL will look like:
post
Request
{ "identifier": "cf30cfba-e62a-4903-99ff-ea3dbac52c8a", "created": "2013-07-09 12:12:01", "type": "transaction.succeeded", "transaction": { "identifier": "1c9b1add-0fec-4e25-8ad9-f314e8bf0a80", "groupIdentifier": "a1afe57f-520d-44cc-8b29-0e3800453481", "customIdentifier": "myX.12390.12309", "referencedTransactionIdentifier": null, "amount": 3.14, "currency": "EUR", "created": "2013-09-03 12:38:36", "status": "APPROVED", "subject": "A bunch of flowers", "type": "CHARGE", "mode": "TEST" "statusDetails": {…}, "locationDetails": {…}, "processingDetails": {…}, "paymentDetails": {…}, "receiptDetails": {…}, "reader": {…} } }
We send the raw JSON with a
Content-Type
of
application/json
. Please make sure that you access the HTTP body accordingly. Depending on the language that you implement your WebHook in, this might be considerably different from the way you access POST parameters sent by HTML forms (those usually have a
Content-Type
of
application/x-www-form-urlencoded
).
The JSON contains the event with its
identifier
(referred to as
eventIdentifier
),
type
and
created
date (in UTC).
transaction
already contains all details about the transaction, like your
transactionIdentifier
.
For a transaction that results in a
status
of
DECLINED
,
ABORTED
or
ERROR
a
transaction.failed
event is fired, also containing all the details in the
transaction
dictionary:
post
Request
{ "identifier": "cf30cfba-e62a-4903-99ff-ea3dbac52c8a", "created": "2013-07-09 12:12:01", "type": "transaction.failed", "transaction": {…} }
Should your app during a transaction crash or lose the connection to the platform for any reason, your transaction will remain in the
status
PENDING
for about 15 minutes until it runs into a timeout. Its
status
will then automatically turn into
ERROR
and you will receive a
transaction.failed
event.
Respond to the Incoming Event Data
Respond with a HTTP status code of
200
,
201
, or
202
to indicate that your server received and processed the call. If you respond with another status code, we consider the event delivery unsuccessful. The platform then tries to send you this transaction event again after 30 seconds and each hour, for up to 3 days to make absolutely sure you do not lose information about your transactions.
Verify Signature to Confirm Authenticity
In theory, anyone can call your WebHook endpoint and send event and transaction data. To make sure that the event happened and that the request is coming from Payworks, we recommend verifying the signature embedded in the WebHook request.
The signature is based on asymmetric cryptography -- we sign every request using a private key and a client can verify the signature using a public key, loaded in a secure way from our public endpoint.
The Public Key
Download the public key using an API endpoint:
get
/signingKeys/public
Request
{ "kid": "<uuid>", "value": "<key>", "alg": "RSA" }
The
kid
field identifies the key, the key itself is Base64 encoded. While we don't plan on rotating keys in the near future, for security reasons we reserve the right to change it.
JWT Signature
Every request contains an authorization header containing a JWT token:
Authorization: Bearer <token>
. The token structure adheres to the JWT standard:
Header: { "kid": "<uuid>", "alg": "RS256", "typ": "JWT" } Claims: { "iat": <timestamp>, "iss": "payworks", "digest": "<digest>", "digestAlgorithm": "SHA-256" } Signature: RS256Hash(base64Encode(header) + “.” + base64Encode(claims), private_key)
The
digest
claims field contains a SHA-256 hash of the request body, to verify that the body content was not modified.
Verification Steps
  1. Load the public key using the described endpoint and cache it.
  2. Extract the JWT token from the WebHook request.
  3. Compare the
    kid
    header value with the downloaded public key ID.
  4. Verify the signature of the request using the public key.
  5. Extract the
    digest
    claims value.
  6. Calculate a SHA-256 hash of the request body.
  7. Compare the hash value with the digest value -- for a non-tampered request, the values must be equal.
We recommend using an external library for parsing and verifying the JWT token.