Webhooks.

Welcome to the Gifty webhooks documentation. This documentation covers all information relevant to our event-driven webhooks.

If you have any questions, don't hesitate to reach out. You can contact us via our contact page.

Introduction

The following section describes generic usage information about webhooks.

Getting started

To start receiving webhooks from us, you'll need to register a webhook on the developer's page in your dashboard

When setting up your new webhook, you'll be asked to fill in several fields. These are explained below:

Attribute Description
Name Used to help you identify different webhooks. This field is only visible to you.
URL This is the endpoint that we forward the events to. This must use HTTPS and accept POST requests.
Events The events you want to subscribe to.
Secret A private key used to verify webhook authenticity. For details on using this security feature, see "Verifying the authenticity".
Active Specifies whether the webhook is active. When paused, it will not send or store events.

Example flow

In this flow, we'll show you how to verify the authenticity of a webhook request. After following these steps correctly, you can be sure that the request is authentic and originates from Gifty.

  • We send a webhook to your endpoint
  • You receive the webhook request
  • Verify the authenticity of the request (HMAC and timestamp)
  • Process the request

Verifying the authenticity

Every webhook includes the X-Gifty-Webhook-Signature header. This header contains a hash of the payload (body) using HMAC-SHA256, with the secret key you provided during the registration of the webhook. By hashing the payload of the request yourself using the same hashing algorithm and the secret key that only you and Gifty know, you can verify if the request is originating from Gifty.

Verifying the signature
          
            const crypto = require("crypto");

const secret = "your_secret";
const payload = "payload_from_webhook";

const signature = crypto.createHmac("sha256", secret)
                      .update(payload)
                      .digest("hex");
            
          
      
          
            import hmac
import hashlib

secret = 'your_secret'
payload = 'payload_from_webhook'

signature = hmac.new(
  secret.encode(),
  payload.encode(),
  hashlib.sha256
).hexdigest()
            
          
      
          
            $secret = 'your_secret';
$payload = 'payload_from_webhook';

$signature = hash_hmac('sha256', $payload, $secret);
            
          
      

Idempotence

Duplicate webhook deliveries can never be completely ruled out, as they may also occur outside our network, for example, due to retries by intermediaries or client-side issues. Because of this, you should never assume that a webhook will be delivered only once. Always design and build your integration to handle potential duplicates safely and idempotently.

You can verify whether a webhook has already been processed by tracking the X-Gifty-Webhook-Job-Id header. This identifier is unique to each individual event and remains the same if the same webhook is resent, such as during a retry. By keeping a record of processed job IDs, you can detect and safely ignore duplicates.

Headers

Every webhook we send comes with a set of headers, that are described as follows:

Header Description
X-Gifty-Webhook-Job-Id The unique identifier of this specific webhook request.
X-Gifty-Webhook-Id The unique identifier of the webhook.
X-Gifty-Webhook-Signature The signature used to validate the authenticity of the request.
X-Gifty-Webhook-Event The event that caused the webhook to be sent.
X-Gifty-Webhook-Version The version of our webhooks processor used to send the request.

Status codes

When your server responds to a webhook request, the HTTP status code you return determines how our system interprets the outcome. Please ensure that your endpoints return the appropriate status code based on the result of your processing logic.

Code Meaning
2XX Successful request.
3XX* Redirections will not be followed, please change the endpoint URL if it changed.
4XX* The request cannot be processed due to client-side errors.
429* The endpoint is receiving too many requests.
5XX* The endpoint is unable to process the request at this time.

* We will attempt to deliver this webhook again later using an exponential back-off strategy.

If a Retry-After header is provided, we will use this instead. The maximum value for this header is 86,400 seconds (24 hours) and anything above that will be rounded down to this maximum.

Events

Event giftcard.issue

A gift card has been issued.

Payload
          {
    "id": "whj_XeLNZ7E9vQM0L8pvOw4BpoY3",
    "object": "event",
    "type": "giftcard.issue",
    "created_at": "2025-04-10T14:24:53+00:00",
    "data": {
        "id": "tr_za5Jk8E1ovxQoDNmrpXRxADb",
        "amount": 4000,
        "currency": "EUR",
        "status": "success",
        "type": "issue",
        "description": "Issue amount of '€ 40,00'",
        "is_capturable": false,
        "captured_at": "2025-04-10T14:24:53+00:00",
        "captured_by": {},
        "created_at": "2025-04-10T14:24:53+00:00",
        "giftcard": ...GiftCardObject,
        "object": "transaction"
    }
}
              
      

Event giftcard.redeem

A gift card has been (partially) redeemed.

Payload
          {
    "id": "whj_VmLwJwo19xKjSAkVpyooDJvl",
    "object": "event",
    "type": "giftcard.redeem",
    "created_at": "2025-04-10T14:25:52+00:00",
    "data": {
        "id": "tr_Blp3JyeqRnk2bxod9N2AZwjM",
        "amount": -1000,
        "currency": "EUR",
        "status": "success",
        "type": "redeem",
        "description": "Redeem amount of '€ 10,00'",
        "is_capturable": false,
        "captured_at": "2025-04-10T14:25:52+00:00",
        "captured_by": {},
        "created_at": "2025-04-10T14:25:52+00:00",
        "giftcard": ...GiftCardObject,
        "object": "transaction"
    }
}
              
      

Event transaction.captured

A transaction has been captured.

Payload
          {
    "id": "whj_GmW4O4axk7K3TQblDJ11N1d0",
    "object": "event",
    "type": "transaction.captured",
    "created_at": "2025-04-10T14:25:52+00:00",
    "data": {
        "id": "tr_Blp3JyeqRnk2bxod9N2AZwjM",
        "amount": -1000,
        "currency": "EUR",
        "status": "success",
        "type": "redeem",
        "description": "Redeem amount of '€ 10,00'",
        "is_capturable": false,
        "captured_at": "2025-04-10T14:25:52+00:00",
        "captured_by": {},
        "created_at": "2025-04-10T14:25:52+00:00",
        "giftcard": ...GiftCardObject,
        "object": "transaction"
    }
}
              
      

Event location.created

A new establishment has been added to the account.

Payload
          {
    "id": "whj_lvqMRMONllO7T0pOB3PxEavN",
    "object": "event",
    "type": "location.created",
    "created_at": "2025-04-10T14:24:28+00:00",
    "data": {
        "id": "lc_NaxRrj0MXmGPRLdp5eok8bKZ",
        "street": "Floridalaan",
        "house_number": "8",
        "addition": null,
        "postal_code": "3404WV",
        "city": "IJsselstein",
        "country_code": "NL",
        "geometry": {},
        "object": "location"
    }
}
              
      

Event location.updated

An establishment has been modified.

Payload
          {
    "id": "whj_QmyxMxq3WNelIybjO7gB9yvP",
    "object": "event",
    "type": "location.updated",
    "created_at": "2025-04-10T14:24:32+00:00",
    "data": {
        "id": "lc_NaxRrj0MXmGPRLdp5eok8bKZ",
        "street": "Floridalaan",
        "house_number": "8",
        "addition": null,
        "postal_code": "3404WV",
        "city": "IJsselstein",
        "country_code": "NL",
        "geometry": {},
        "object": "location"
    }
}
              
      

Event location.deleted

An establishment has been removed from the account.

Payload
          {
    "id": "whj_lvgRQRqJXPZru8V4A38ALrng",
    "object": "event",
    "type": "location.deleted",
    "created_at": "2025-04-10T14:24:35+00:00",
    "data": {
        "id": "lc_NaxRrj0MXmGPRLdp5eok8bKZ",
        "street": "Floridalaan",
        "house_number": "8",
        "addition": null,
        "postal_code": "3404WV",
        "city": "IJsselstein",
        "country_code": "NL",
        "geometry": {},
        "object": "location"
    }
}
              
      
language