WebhooksImprove this page
Fidel API uses webhooks to notify your application when relevant events happen in your account across multiple resources, namely with event types such as brand.consent
, card.failed
, card.linked
, location.status
, marketplace.offer.live
, marketplace.offer.updated
, program.status
, transaction.auth.qualified
, transaction.auth
, transaction.clearing.qualified
, transaction.clearing
, transaction.refund.qualified
and transaction.refund
.
Fidel API will notify your registered webhook URLs as the event happens, via a HTTP POST request with a signature header for verification, which needs to be received and acknowledged in a timely manner. The HTTP request contains the event object as payload.
For example, when a customer makes a payment with a linked Mastercard card, on a participating location, a transaction.auth
event is sent in real-time to the specified webhook URL, with a payload that contains the Transaction object.
Management and behavior
There are two ways you can manage your webhooks, i.e., view, create, update, delete, with the Fidel API. You can create them in the Fidel Dashboard, under the "Webhooks" page or make HTTP requests using the Webhooks API.
As requirements for creation, Fidel API only accepts HTTPS URLs for webhook endpoints, thus your server must support HTTPS and have a valid certificate.
Fidel API sends the data via HTTP POST in JSON format. It will send test events if your Dashboard is in test mode or if you are using test API keys when registering the webhook URLs. To receive live events, flip your switch on the Dashboard to go live, or create the webhooks using a live API key.
Fidel API has two types of webhooks (brand and program-related), both of which work similarly, but have slightly different requirements to be registered. For customization, we also allow additional HTTP headers to be added to help integrating with your systems.
Brand-related webhooks
The brand.consent
webhook only requires an URL
to register. You can use the Hooks endpoint for registering brand webhooks.
Here's an example on how to create one using the Fidel API, with example.com
as the URL:
123456789curl -X POST \ https://api.fidel.uk/v1/hooks \ -H 'Content-Type: application/json' \ -H 'Fidel-Key: <KEY>' \ -d '{ "event": "brand.consent", "url": "https://example.com" }'
Program-related webhooks
Program webhooks require a programId
to be associated with and a URL
to register. You can register up to 10 webhook URLs per event type for each program. You can use the Program Hooks endpoint for registering program webhooks. The events that can be registered for a given program are card.linked
, card.failed
, program.status
, location.status
, transaction.auth
, transaction.auth.qualified
, transaction.clearing
, transaction.clearing.qualified
, transaction.refund
and transaction.refund.qualified
.
Here's an example on how to create a webhook on a Program for the transaction.auth
event, with example.com
as the URL:
123456789curl -X POST \ https://api.fidel.uk/v1/programs/06471dbe-a3c7-429e-8a18-16dc97e5cf35/hooks \ -H 'Content-Type: application/json' \ -H 'Fidel-Key: <KEY>' \ -d '{ "event": "transaction.auth", "url": "https://example.com" }'
Acknowledging reception
To confirm receipt of a webhook event, your server endpoint should return a 200 OK
HTTP status code. Any other response, or not providing any response within 20 seconds will be treated as a failure and our system will retry sending the request twice (i.e. three tries in total), with one-minute wait on the second request and two-minute wait on the third (last) attempt.
To avoid timeouts, it is recommended to not run complex and time-consuming logic upon reception of the webhook in order to provide a response back and thus avoid unnecessary retries and potentially duplicate processing of the events.
Custom request headers
Fidel API allows you to define custom HTTP headers when you register a webhook URL. The custom headers are included in the HTTP POST request headers that are sent to your application.
Custom headers can be defined when creating a new webhook in the Dashboard or by using the Webhooks API and setting the optional headers
object with a key-value pair (see example below).
123456789101112curl -X POST \ https://api.fidel.uk/v1/programs/06471dbe-a3c7-429e-8a18-16dc97e5cf35/hooks \ -H 'Content-Type: application/json' \ -H 'Fidel-Key: <KEY>' \ -d '{ "event": "transaction.auth", "url": "https://example.com", "headers": { "Custom-Header": "my-custom-header" } }'
To delete custom headers from a registered webhook, use the Update Hooks endpoint and send an empty headers
object.
1234567891011curl -X PUT \ https://api.fidel.uk/v1/hooks/3b4be60b-6596-4b40-ae3d-89b9fdaf132a \ -H 'Content-Type: application/json' \ -H 'Fidel-Key: <KEY>' \ -d '{ "programId": "06471dbe-a3c7-429e-8a18-16dc97e5cf35", "event": "transaction.auth", "url": "https://example.com", "headers": {} }'
A maximum of 5 custom headers per webhook can be defined, and they need to follow strict character validation patterns. The key name must be between 1 and 64 characters and only accepts the Roman alphabet, numbers, dashes and underscores. The value must be between 1 and 1000 characters. Additionally, the HTTP reserved headers are blocklisted and cannot be used for the key name. The full list of blocklisted key names:
1234567891011121314151617181920212223242526272829303132333435363738394041424344[ "Accept-Charset", "Accept-Datetime", "Accept-Encoding", "Accept-Language", "Accept", "Access-Control-Request-Headers", "Access-Control-Request-Method", "Cache-Control", "Connection", "Content-Length", "Content-Type", "Cookie", "Date", "Expect", "Fidel-Account", "Fidel-Key", "Fidel-Live", "Fidel-Request-Id", "Fidel-User", "Forwarded", "From", "Host", "If-Match", "If-Modified-Since", "If-None-Match", "If-Range", "If-Unmodified-Since", "Max-Forwards", "Origin", "Pragma", "Proxy-Authorization", "Range", "Referer", "TE", "Transfer-Encoding", "Upgrade", "User-Agent", "Via", "Warning", "X-Fidel-Signature", "X-Fidel-Timestamp" ]
Brand
A brand.consent
event is triggered when Brand consent is “Approved”. In the test
environment, it will immediately trigger after you create a Brand. In the live
environment, it will trigger when the Brand User approves the consent request.
brand.consent
1234567891011{ "id": "e44a9220-5b46-42cf-a944-31f0674bf8f8", "accountId": "1a269963-de11-4ab5-b82d-638a324ff09e", "consent": true, "created": "2018-10-19T13:29:40.922Z", "live": true, "name": "Starbucks", "updated": "2018-01-20T13:29:40.922Z", "logoURL": "https://example.com/" }
Program
A program.status
event is triggered in the live
environment when there are updates for each Location in a Program. In the test
environment, this webhook doesn't trigger because there is no concept of Location syncing there.
program.status
1234567891011121314151617{ "id": "f67b7b16-0e3d-457a-811a-cc21dc8448f3", "accountId": "77aa1f5f-7152-45dc-8eb1-cb83355e9cf9", "active": true, "activeDate": "2018-10-28T09: 33: 30.139Z", "created": "2018-10-26T17: 10: 02.509Z", "live": true, "name": "Starbucks", "syncStats": { "created": "2020-04-23T11:10:40.054Z", "locations": 2, "estimatedEndDate": "2020-05-07T11:10:40.054Z", "status": "syncing" }, "updated": "2018-10-30T16: 12: 15.604Z" }
Location
A location.status
event is triggered when there are updates from a card network for a Location in a Program. In the test
environment, this webhook triggers three times for each location upon creating a Location, once for each card scheme, with a location.active
event. In the live
environment, this would trigger whenever a location has synced successfully for a card scheme, with a location.active
event. Or whenever a location has failed to sync for a card scheme, with a location.failed
event.
location.status
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556{ "location": { "city": "London", "timezone": "Europe/London", "mastercard": { "estimatedActivationDate": null, "clearingTransactionId": null, "auth": false, "authTransactionId": null, "clearing": false, "status": "inactive" }, "countryCode": "GBR", "activeDate": "2020-08-18T13:40:11.406Z", "currency": "GBP", "id": "298d9c88-cabe-4583-a54b-574e29b57c84", "live": false, "address": "1 Main Street", "created": "2020-08-18T13:40:11.406Z", "postcode": "W1NN3R", "searchBy": { "merchantIds": { "visa": [], "mastercard": [] } }, "accountId": "36081095-2782-4669-8a07-857bbaaeb89b", "amex": { "estimatedActivationDate": null, "clearingTransactionId": null, "auth": false, "authTransactionId": null, "clearing": false, "status": "active" }, "visa": { "estimatedActivationDate": null, "clearingTransactionId": null, "auth": false, "authTransactionId": null, "clearing": false, "status": "inactive" }, "brandId": "9cd32c61-43ca-4bb7-8aca-0cf491112c28", "preonboard": false, "updated": "2020-08-18T13:40:11.406Z", "programId": "f2c9719a-6433-4ef4-8401-19d7ebf60ab9", "geolocation": { "latitude": 51.5138332, "longitude": -0.1318224 } }, "scheme": "amex", "status": "location.active" }
Card
There are two card-related events available: card.linked
and card.failed
. They are useful for linking a card if you want to receive the response via your server instead of via the client when using the SDK callbacks.
A card.linked
event is triggered when a card is successfully linked.
card.linked
1234567891011121314151617{ "id": "556f8179-bfdb-4274-852e-ba6e43559590", "accountId": "d346d574-d5c2-4a0e-8e02-ffd813fd1a8d", "countryCode": "GBR", "created": "2018-10-29T17:01:05.013Z", "expDate": "2019-10-01T00:00:00.000Z", "expMonth": 10, "expYear": 2020, "firstNumbers": "444400", "lastNumbers": "4735", "live": false, "programId": "06471dbe-a3c7-429e-8a18-16dc97e5cf35", "scheme": "visa", "type": "visa", "updated": "2018-10-29T17:01:05.013Z" }
A card.failed
event is triggered when card linking fails. This payload includes the event name and error message.
card.failed
123456789101112131415161718192021{ "card": { "id": "c3730d4d-9915-4c51-831f-14b8aeeb56b7", "accountId": "d346c574-d5c2-4a0e-8e02-ffd713fd1a8d", "countryCode": "GBR", "created": "2018-10-29T17:34:54.693Z", "expDate": "2020-10-01T00:00:00.000Z", "expMonth": 10, "expYear": 2020, "firstNumbers": "528245", "lastNumbers": "6015", "live": true, "programId": "06471dbe-a3c7-429e-8a18-16dc97e5cf35", "scheme": "mastercard", "type": "mastercard", "updated": "2018-10-29T17:34:56.380Z" }, "event": "card.failed", "message": "Error linking card." }
Transaction
A transaction.auth
or authorisation transaction event is triggered when a transaction is carried out on a linked card. For example, when a customer is making a payment in-store in real-time. When a customer makes a payment with a linked debit/credit card in an auth-enabled location, the transaction.auth
webhook is triggered and the transaction object sent to your specified URL in real-time.
transaction.auth
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758{ "id": "7fdfd5d8-9589-402f-8477-4a727ad239a2", "accountId": "4ed4b62b-aa4c-43a1-8064-da6d1368e17a", "programId": "06471dbe-a3c7-429e-8a18-16dc97e5cf35", "datetime": "2019-03-12T19:12:01", "created": "2019-03-12T19:12:01.744Z", "updated": "2019-03-12T19:12:01.744Z", "offer": null, "approvalCode": "AA00BB", "auth": true, "authCode": "A73H890", "cardPresent": true, "cleared": false, "amount": 100, "currency": "GBP", "wallet": null, "card": { "id": "bc538b71-31c5-4699-840a-6d4a08693314", "firstNumbers": "555500", "lastNumbers": "5001", "scheme": "visa", "metadata": { "name": "fancy card", "id": "card-id" } }, "brand": { "id": "9d136f2e-df99-4a08-a0a5-3bc1534b7db9", "name": "Bob's Cafe", "logoURL": null, "metadata": null, }, "location": { "id": "7a916fbd-70a0-462f-8dbc-bd7dbfbea160", "address": "2 Soho Square", "city": "London", "postcode": "W1D3PX", "countryCode": "GBR", "timezone": "Europe/London", "geolocation": { "latitude": 51.5152346, "longitude": -0.1310718 }, "metadata": { "id": "your-user-id", "name": "username" } }, "identifiers": { "MID": "12345678", "mastercardAuthCode": null, "mastercardTransactionSequenceNumber": "0000000000000", "mastercardRefNumber": "AABBCCDDE", "amexApprovalCode": "AA00BB", "visaAuthCode": "A73H890" } }
A transaction.clearing
or clearing transaction event is triggered when a transaction is settled, usually happens 48 to 72 hours after a payment is made. The Fidel processes for clearing transactions and triggering the transaction.clearing
webhook events run daily at 12:00 UTC for Mastercard and multiple times per day for Visa and American Express. Only one transaction is sent per event.
transaction.clearing
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748{ "approvalCode": "AA00BB", "auth": true, "authCode": "A73H890", "offer": null, "currency": "GBP", "id": "8bbbf56b-3819-473b-877d-cf2175f268f4", "amount": 10, "wallet": null, "created": "2020-07-08T17:05:52.567Z", "accountId": "36081095-2782-4669-8a07-857bbaaeb89b", "cardPresent": false, "cleared": true, "updated": "2020-07-08T17:20:44.134Z", "programId": "06471dbe-a3c7-429e-8a18-16dc97e5cf35", "datetime": "2020-07-08T18:05:52", "card": { "id": "62744670-f935-4ba3-8e89-be23e31292cf", "firstNumbers": "444400", "lastNumbers": "4305", "scheme": "visa" }, "location": { "address": "5 Main St", "city": "Bristol", "countryCode": "GBR", "id": "0ff99cfd-9e8b-48fe-af55-2519ffe1c0a4", "geolocation": null, "postcode": "BS2 5BL", "timezone": "Europe/London", "metadata": null }, "brand": { "id": "966481e7-dd6f-44d2-83fa-98783aaacf40", "name": "Bob's Cafe", "logoURL": null, "metadata": null }, "identifiers": { "MID": "TEST_MID_d74bc1ca-cd6e-409d-ba8e-549a214dfb0a", "mastercardTransactionSequenceNumber": null, "mastercardRefNumber": null, "mastercardAuthCode": null, "amexApprovalCode": "AA00BB", "visaAuthCode": "A73H890" } }
A transaction.refund
or refund transaction event is triggered when a transaction is refunded. Besides the transaction.refund
event, a refunded transaction also triggers a transaction.clearing
event. On both, their auth
property is set to false
and the amount is negative. Fidel API tries to identify the initial transaction for which the refund was issued, using cardId
, locationId
, merchantId
, amount
and datetime
. If an associated initial transaction is identified, the webhook data contains the originalTransactionId
. If no initial transaction is identified, the data comes in on both webhooks with a negative amount but no originalTransactionId
property.
transaction.refund
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849{ "approvalCode": "AA00BB", "auth": false, "authCode": "A73H890", "originalTransactionId": "8bbbf56b-3819-473b-877d-cf2175f268f4", "currency": "GBP", "id": "5ec08ca8-38c6-42e1-9fa5-32c67e4135b2", "amount": -10, "wallet": null, "created": "2020-07-08T17:23:13.972Z", "accountId": "36081095-2782-4669-8a07-857bbaaeb89b", "cardPresent": false, "cleared": true, "updated": "2020-07-08T17:23:13.972Z", "programId": "06471dbe-a3c7-429e-8a18-16dc97e5cf35", "datetime": "2020-07-08T18:23:13", "card": { "id": "62744670-f935-4ba3-8e89-be23e31292cf", "firstNumbers": "444400", "lastNumbers": "4305", "scheme": "visa" }, "location": { "address": "5 Main St", "city": "Bristol", "countryCode": "GBR", "id": "0ff99cfd-9e8b-48fe-af55-2519ffe1c0a4", "geolocation": null, "postcode": "BS2 5BL", "timezone": "Europe/London", "metadata": null }, "brand": { "id": "966481e7-dd6f-44d2-83fa-98783aaacf40", "name": "Bob's Cafe", "logoURL": null, "metadata": null }, "identifiers": { "MID": "TEST_MID_d74bc1ca-cd6e-409d-ba8e-549a214dfb0a", "mastercardTransactionSequenceNumber": null, "mastercardRefNumber": null, "amexApprovalCode": null, "mastercardAuthCode": null, "amexApprovalCode": "AA00BB", "visaAuthCode": "A73H890" } }
There are three additional transaction webhook events: transaction.auth.qualified
, transaction.clearing.qualified
and transaction.refund.qualified
. They are triggered when an auth
, clearing
or a refund
transaction is qualified for an Offer.
transaction.auth.qualified
and transaction.clearing.qualified
events are triggered only if transactions they are inside of the offer's period and pass the set of rules defined in the offer. transaction.refund.qualified
events might be emitted even when the offer has expired due to the complex matching logic of the refund to the original transaction.
The payload for these events includes the offer
object with the results of the qualification, and you can read more about it in the Offers API documentation.
123456789"offer": { "qualified": true, "id": "eeefb94b-d11c-44db-81d7-d86a9fcc4069", "message": [], "qualificationDate": null, "cashback": 2.5, "performanceFee": 0.3 }
You can filter transactions from a specific offer by setting the offerId
optional property when creating a transaction qualification webhook event.
12345678910curl -X POST \ https://api.fidel.uk/v1/programs/06471dbe-a3c7-429e-8a18-16dc97e5cf35/hooks \ -H 'Content-Type: application/json' \ -H 'Fidel-Key: <KEY>' \ -d '{ "event": "transaction.auth.qualified", "url": "https://example.com", "offerId": "cf22478e-c700-4f31-b75b-38016605e2a3" }'
Signatures
If you want to confirm that incoming requests on your webhook URL are coming from the Fidel API, we recommend verifying webhook signatures. We send the x-fidel-signature
and x-fidel-timestamp
HTTP headers for each request we make to a webhook URL.
Fidel API generates a unique secret key for each webhook you register. The key is returned in the response's secretKey
property if you are using the Webhooks API. You can also copy the key from the Fidel Dashboard's Webhooks page by clicking in the Show Key button next to your webhook endpoint. To verify a webhook request, generate a signature using the same key that the Fidel API uses and compare that to the value of the x-fidel-signature
header.
Replay attacks are a common MITM attack vector where a valid payload and its signature is intercepted and re-transmitted. If you want to safeguard against them, you can use the x-fidel-timestamp
header and confirm that the timestamp is not too old. We recommend you validate the requests in a 5-minute gap. In the case of retries, a new signature and timestamp are generated for each retry request.
The valuation/verification can be conducted as follows:
- Create a string concatenating the body of the request, the webhook URL and the timestamp value from the
x-fidel-timestamp
header. - Double hash the resulting string using the webhook
secretKey
with HMAC-SHA256 and encode it in Base-64. - Compare the signature you generated with the signature provided in the
x-fidel-signature
header.
Example JavaScript implementation
1234567891011121314151617181920/** fidelHeaders - x-fidel-signature and x-fidel-timestamp headers payload - request payload (body) secret - webhook secretKey url - webhook URL */ function isSignatureValid(fidelHeaders, payload, secret, url) { function base64Digest(s) { return crypto.createHmac("sha256", secret).update(s).digest("base64"); } /** You can check how much time has passed since the request has been sent */ /** timestamp - UTC Unix Timestamp (milliseconds) */ const timestamp = fidelHeaders["x-fidel-timestamp"]; const content = JSON.stringify(payload) + url + timestamp; const signature = base64Digest(base64Digest(content)); return fidelHeaders["x-fidel-signature"] === signature; }
Deleting Webhooks
You can delete webhooks in the Fidel API Dashboard, or using the API's Delete Webhook endpoint.
12345curl -X DELETE \ https://api.fidel.uk/v1/hooks/b9ef3795-a38f-4ef2-8d8d-293dd7fbe1a7 \ -H 'content-type: application/json' \ -H 'fidel-key: sk_test_50ea90b6-2a3b-4a56-814d-1bc592ba4d63'
API Reference
If you're looking to find out more about our Webhooks API and how to use it with your application, please visit the Fidel API Reference.