Webhook notifications [PayDirect]
You can follow deposit and withdrawal events for PayDirect through webhook notifications.
A webhook is successfully delivered when we receive a success status code (2xx) from the webhook URI that you specified in your Console settings for PayDirect.
If we receive any other status code (for instance, if your API is temporarily unavailable), we will start retrying. Our retry policy is jittered exponential backoff. We will immediately perform some fast retries and, if those fail, start waiting increasingly longer. We will keep retrying for up to 72 hours.
Validate the received webhook signature
We recommend that you use our signing libraries to verify the Tl-Signature
of the received webhooks.
E.g. Java com.truelayer.truelayer-signing
Verifier.verifyWithJwks(jwks)
.method("POST")
.path(path)
.headers(allWebhookHeaders)
.body(body)
.verify(webhookSignature);
Handling duplicated webhooks
TrueLayer can’t guarantee that you’ll only receive a single webhook notification for each payment status. As such, your integration should have logic that can handle receiving multiple webhooks for a given payment status.
For example, imagine TrueLayer sends an executed
webhook, but doesn’t receive a 200 response due to network issues from the recipient. In this case, TrueLayer sends an extra executed
webhook as it cannot confirm the previous one was received – regardless of the current status of the payment. The recipient’s integration should be able to handle such possibilities.
Webhook Structure
All webhooks from the PayDirect API are structured as follows:
Header
Field | Type | Description |
---|---|---|
X-TL-Webhook-Timestamp | timestamp | The time that the webhook was sent to you. This will be in the following format: 2020-05-18T10:17:47Z. |
Tl-Signature | string | JSON web signature with a detached payload of the form {HEADER}..{SIGNATURE} |
X-Tl-Signature | string | Legacy JWS. Verify using Tl-Signature if at all possible. |
Body
Field | Type | Description |
---|---|---|
event_type | string | Describes which event is detailed. |
event_schema_version | unsigned integer | The version of the event body schema (for example, 1). |
event_id | uuid | The unique ID of the event. |
event_body | object | Containing the object relating to the event. The body schema is specified in the following sections for each event type. |
Validating the webhook signature
The value of the X-Tl-Signature
header contains a JSON Web Signature (JWS) with a detached payload. It takes the form {HEADER}..{SIGNATURE}
.
The JWS header contains the following values:
Field | Type | Description |
---|---|---|
alg | string | The algorithm used to sign the webhook. We currently use the RS512 algorithm. |
jku | string | The URI of the JSON Web Key Set (JWKS), a hosted page where a list of public keys owned by TrueLayer can be found. |
kid | string | The ID of the key used to sign the webhook, used to retrieve the correct key from the JWKS. |
iat | int | The time that the JWS was issued, represented in Unix time (number of seconds since 1970-01-01T00:00:00Z). |
The steps for verifying the signature are as follows:
- Verify the
jku
field in the token header matches the expected value as listed below. If not, the signature should be rejected as invalid.- Sandbox:
https://webhooks.truelayer-sandbox.com/.well-known/jwks
- Production:
https://webhooks.truelayer.com/.well-known/jwks
- Sandbox:
- Use the
jku
value to retrieve the JWKS. This may be cached for some time, but if verification fails, the JWKS must be retrieved again to ensure that all keys are up to date. TrueLayer may rotate or revoke keys at any time. - Retrieve the relevant public key by using the
kid
to look up the relevant value from the JWKS. - Use a JWT library to verify the JWS headers combined with the payload (the webhook request body) matches the signature provided in the token. The algorithm used to verify the signature must be that declared in the
alg
header of the JWS.
Deposits
You can find the relevant fields in the event_body
for the following event_type
s:
Event type | Field | Parameter type | Description |
---|---|---|---|
|
client_id |
string | A unique string identifying a client. |
deposit_id |
uuid | Unique ID of the deposit. | |
user_id |
uuid | Unique ID of the user. |
Here is an example deposit_initiated event:
X-TL-Webhook-Timestamp: 2020-05-18T10:17:52Z
Tl-Signature: "detached..jws"
{
"event_type": "deposit_initiated",
"event_id": "714040e3-288e-468d-9575-e5116ff0a9fc",
"event_schema_version": 1,
"event_body": {
"client_id": "client-8806da",
"deposit_id": "bd780cca-46a6-4f28-a0e4-d36e4e8c57c2",
"user_id": "8c2f148a-c026-4ce4-8fe4-6afac4f71b6e"
}
}
For the deposit_settled event_type
, the event_body
contains:
Field | Type | Description |
---|---|---|
client_id | string | A unique string identifying a client. |
transaction_id | uuid | The unique ID of the transaction. |
deposit_id | uuid | The unique ID of the deposit. |
user_id | uuid | The unique ID of the user. |
account_id | uuid | The unique ID of the user's account. |
settled_at | timestamp | The date and time that the deposit was settled into the account. |
amount_in_minor | integer | The amount received in the smallest denomination possible. |
currency | ISO 4217 Currency Code String | The currency the top-up was settled in. |
remitter_iban | string | The IBAN of the account that sent the money. |
remitter_name | string | The name of the account holder that sent the money. |
Here is an example deposit_settled event:
X-TL-Webhook-Timestamp: 2020-05-18T10:17:52Z
Tl-Signature: "detached..jws"
{
"event_type": "deposit_settled",
"event_id": "33c9fc5b-69d7-4de0-83a9-8177f9af79d2",
"event_schema_version": 1,
"event_body": {
"client_id": "client-8806da",
"transaction_id": "cc328607-e02e-49e2-81c9-5bd044c8f7d7",
"deposit_id": "88e76e9e-811a-4f02-9624-f808c5925bfb",
"user_id": "bec96bf3-788b-4266-92dc-77351b680ac5",
"account_id": "5156e5af-7bf2-49a4-96b3-7fde5ec8ddfe",
"settled_at": "2019-10-01T17:00:00.0000000Z",
"amount_in_minor": 10000,
"currency": "GBP",
"remitter_iban": "GB33BUKB20201555555555",
"remitter_name": "Bob Brown"
}
}
For the external_deposit_received event_type
, the event_body
contains:
Field | Type | Description |
---|---|---|
client_id | string | A unique string identifying a client. |
transaction_id | uuid | The unique ID of the transaction. |
received_at | timestamp | The date and time that the external deposit was received. |
amount_in_minor | integer | The amount received, in the smallest denomination possible. |
currency | ISO 4217 Currency Code String | The currency the transaction was settled in. |
remitter_iban | string | The IBAN of the account that sent the money. |
remitter_name | string | The name of the account holder that sent the money. |
reference | string | The reference which identifies the payment. |
Here is an example external_deposit_received event:
X-TL-Webhook-Timestamp: 2020-05-18T10:17:52Z
Tl-Signature: "detached..jws"
{
"event_type": "external_deposit_received",
"event_id": "33c9fc5b-69d7-4de0-83a9-8177f9af79d2",
"event_schema_version": 1,
"event_body": {
"client_id": "client-8806da",
"transaction_id": "cc328607-e02e-49e2-81c9-5bd044c8f7d7",
"received_at": "2019-10-01T17:00:00.0000000Z",
"amount_in_minor": 10000,
"currency": "GBP",
"remitter_iban": "GB33BUKB20201555555555",
"remitter_name": "Bob Brown",
"reference": "7820IPXNJ1X6QFKRHR"
}
}
Withdrawals
The withdrawal_authorised event body contains the following fields:
Field | Type | Description |
---|---|---|
client_id | string | A unique string identifying a client. |
transaction_id | uuid | The unique ID of the withdrawal. |
authorised_at | Timestamp | The date and time that the withdrawal was authorised at. |
Here's what that looks like:
X-TL-Webhook-Timestamp: 2020-05-18T10:17:52Z
Tl-Signature: "detached..jws"
{
"event_type": "withdrawal_authorised",
"event_id": "33c9fc5b-69d7-4de0-83a9-8177f9af79d2",
"event_schema_version": 1,
"event_body": {
"client_id": "client-8806da",
"transaction_id": "cc328607-e02e-49e2-81c9-5bd044c8f7d7",
"authorised_at": "2019-10-01T17:00:00.0000000Z"
}
}
The withdrawal_submitted event body contains the following fields:
Field | Type | Description |
---|---|---|
client_id | string | A unique string identifying a client. |
transaction_id | uuid | The unique ID of the payout. |
submitted_at | Timestamp | The date and time that the payout was submitted to the scheme. |
Here's what that looks like:
X-TL-Webhook-Timestamp: 2020-05-18T10:17:52Z
Tl-Signature: "detached..jws"
{
"event_type": "withdrawal_submitted",
"event_id": "33c9fc5b-69d7-4de0-83a9-8177f9af79d2",
"event_schema_version": 1,
"event_body": {
"client_id": "client-8806da",
"transaction_id": "cc328607-e02e-49e2-81c9-5bd044c8f7d7",
"submitted_at": "2019-10-01T17:00:00.0000000Z"
}
}
The withdrawal_settled event body contains the following fields:
Field | Type | Description |
---|---|---|
client_id | string | A unique string identifying a client. |
transaction_id | uuid | The unique ID of the payout. |
settled_at | Timestamp | The date and time that the payout was settled into the beneficiary account. |
Here's what that looks like:
X-TL-Webhook-Timestamp: 2020-05-18T10:17:52Z
Tl-Signature: "detached..jws"
{
"event_type": "withdrawal_settled",
"event_id": "33c9fc5b-69d7-4de0-83a9-8177f9af79d2",
"event_schema_version": 1,
"event_body": {
"client_id": "client-8806da",
"transaction_id": "cc328607-e02e-49e2-81c9-5bd044c8f7d7",
"settled_at": "2019-10-01T17:00:00.0000000Z"
}
}
The withdrawal_rejected event body contains the following fields:
Field | Type | Description |
---|---|---|
client_id | string | A unique string identifying a client. |
transaction_id | uuid | The unique ID of the payout. |
rejected_at | Timestamp | The date and time that the payout was rejected. |
rejection_code | String | The reason the payout was rejected in the form of a machine-parsable enum. |
rejection_details | String | The reason the payout was rejected in the form of a human-friendly description. |
Here's what that looks like:
X-TL-Webhook-Timestamp: 2020-05-18T10:17:52Z
Tl-Signature: "detached..jws"
{
"event_type": "withdrawal_rejected",
"event_id": "33c9fc5b-69d7-4de0-83a9-8177f9af79d2",
"event_schema_version": 1,
"event_body": {
"client_id": "client-8806da",
"transaction_id": "cc328607-e02e-49e2-81c9-5bd044c8f7d7",
"rejected_at": "2019-10-01T18:00:00.0000000Z",
"rejection_code": "insufficient_funds",
"rejection_details": "There were not enough funds on the account to authorise the payout."
}
}
The withdrawal_failed event body contains the following fields:
Field | Type | Description |
---|---|---|
client_id | string | A unique string identifying a client. |
transaction_id | uuid | The unique ID of the payout. |
failed_at | Timestamp | The date and time that the payout status moved to failed. |
failure_code | String | The reason the payout failed in the form of a machine-parsable enum. |
failure_details | String | The reason the payout failed in the form of a human-friendly description. |
Here's what that looks like:
X-TL-Webhook-Timestamp: 2020-05-18T10:17:52Z
Tl-Signature: "detached..jws"
{
"event_type": "withdrawal_failed",
"event_id": "33c9fc5b-69d7-4de0-83a9-8177f9af79d2",
"event_schema_version": 1,
"event_body": {
"client_id": "client-8806da",
"transaction_id": "cc328607-e02e-49e2-81c9-5bd044c8f7d7",
"failed_at": "2019-10-01T18:00:00.0000000Z",
"failure_code": "server_error",
"failure_details": "We encountered a technical issue while processing your payment, please try again later."
}
}
Updated 7 months ago