Webhook handler

Follow these intructions to correctly process our webhooks

A webhook handler is a HTTPS endpoint on your server with a URL. You can use one endpoint to handle several different event types at once, or set up individual endpoints for specific events.

Create a webhook endpoint

Set up an HTTPS endpoint that can accept unauthenticated webhook requests with a POST method.

For example using Node.js's Express:

app.post('/shine_webhooks', (req, res) => {
  const payload = req.body;
  // Add your logic here
})

In this example, the /shine_webhooks route is configured to accept only POST requests and expects data to be delivered in a JSON payload.

Handle requests from Shine

Your endpoint must be configured to read event objects for the type of event notifications you want to receive. Shine sends events to your webhook endpoint as part of a POST request with a JSON payload.

Each event is structured as an event object with an event property and the related Shine resource under entity. Your endpoint must check the event type and parse the payload of each event.

{
    "event": "entityType.(create|update)",
    "entity": {} // the created/updated entity
}

Your endpoint must quickly return a successful status code (2xx) prior to any complex logic that could cause a timeout.

Built-in retries

Shine webhooks have built-in retry methods for 4xx, or 5xx response status codes. If Shine doesn’t quickly receive a 2xx response status code for an event, the webhook is sent again using an exponential back-off retry strategy. Delivery is retried up to 20 times.
Once all these attempts have failed, the webhook is discarded and won't be sent again.

Secure your webhook handler

Shine sign each webhook it sends to your endpoints by including a signature in the Shine-Signature header. This allows you to verify that the events were sent by Shine, not by a malicious third party.

To verify signatures, you need to retrieve your endpoint’s secret that was sent to you during the webhook registration process.

Shine generates a unique secret key for each endpoint and secrets aren't shared between environments (sandbox vs production).

Verify the signature

Shine generates signatures using a hash-based message authentication code (HMAC) with SHA-512.

  1. Extract the timestamp and signatures from the corresponding headers
  2. Prepare the signedPayload string created by concatenating the timestamp, the . character and the request JSON body
  3. Determine the expected signature by computing an HMAC with the SHA512 hash function using the endpoint's secret as the key and the signedPayload as the message
  4. Compare the signature in the header with the expected signature.
const crypto = require('crypto')

app.post('/shine_webhooks', (req, res, next) => {
  // 1. Extract the timestamp and signatures from the corresponding headers
  const timestamp = req.headers.date; // Date header
  const signature = req.headers['shine-signature'] // Shine-Signature header

    // 2. Prepare the `signedPayload` string created by concatenating the timestamp, the `.` character and the request JSON body
  const { body } = req; // should be the raw unparsed body as JSON parsing can change fields ordering and alter signature
  const signedPayload = `${timestamp}.${body}`;
  
  // 3. Determine the expected signature by computing an HMAC with the SHA512 hash function using the endpoint's secret as the key and the `signedPayload` as the message
  const hmac = crypto.createHmac('sha512', secret);
  hmac.update(signedPayload);
  const expectedSignature = hmac.digest('hex');
  
  // 4. Compare the signature in the header with the expected signature.
    if(expectedSignature !== signature) {
    next(new Error('Invalid signature'));
    return;
  }
  
  // Add your logic here
})

Prevent replay attacks

A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. To mitigate such attacks, Shine includes a timestamp in the Date header. Because this timestamp is part of the signed payload, it is also verified by the signature, so an attacker cannot change the timestamp without invalidating the signature. If the signature is valid but the timestamp is too old, you can have your server reject the payload.

Shine generates the timestamp and signature once per event. If Shine retries an event (for example, your endpoint previously replied with a non-2xx status code), then the signature and timestamp won't change between retries. You must take this into account when defining if a timestamp is too old.

Handle duplicate events

Webhook endpoints might occasionally receive the same event more than once. We advise you to guard against duplicated event receipts by making your event processing idempotent.

Prevent stale data

As webhooks can be retried, another update can have occured once your server is finally able to process the event. Therefore, we advise you to query the latest version of the related entity upon receiving a webhook.
This will also help mitigate replay attacks.

Handle out of order events

Shine does not guarantee delivery of events in the order in which they are generated. Refer to the previous section to avoid stale data.