> Agent-readable docs index: /llms.txt. Download /docs.zip to grep all markdown files locally.

---
title: "Webhooks Overview & Setup"
description: "Webhooks allow you to receive real-time updates about changes in Rye's data."
---

When integrating with Rye's APIs, you might need your backend to perform actions in response to changes in Rye's system. For example, sending an email to a customer when their order is shipped.

While polling Rye's APIs is possible, it's inefficient and increases system load. To support event-driven architectures, Rye provides webhook functionality. Webhooks enable you to receive real-time updates when specific events occur in Rye's system, such as order placements or product price changes.

**Wherever possible, we recommend using webhooks to receive updates from Rye's system, rather than polling Rye's APIs.**

## Use Cases

Common use cases for webhooks include:

* **Data Synchronization:** Maintain a local copy of Rye's product data for search and display.
* **Customer Notifications:** Send order updates to customers via email or SMS.
* **Internal Alerts:** Trigger alerts when orders fail to process.

## Getting Started with Webhooks

To receive webhooks from Rye, follow these steps:

<Steps>
  <Step title="Navigate to Account Settings">
    Go to the [Rye Console](https://console.rye.com/account).
  </Step>

  <Step title="Set Up an Endpoint">
    Enter your publicly accessible endpoint URL (must be accessible over the internet and capable of handling HTTP POST requests).

    <Image src="/_holocron/images/f8a4b37a-a64deca-image.png" alt="" width="1450" height="696" placeholder="data:image/webp;base64,UklGRjwAAABXRUJQVlA4IDAAAACQAQCdASoQAAgAAsBMJaQAAxZQn+AA/vjbvIQ1jDSYDesNwtsUsdcnpvzvOc4IAAA=" />
  </Step>

  <Step title="URL Verification Handshake">
    After saving your endpoint, Rye will send a verification request containing a `challenge` parameter. Your endpoint must respond with the exact `challenge` value to confirm it can receive webhooks. Rye will only save the URL if the challenge is successfully verified.

    **Verification Request Example:**

    ```json
    {
      "id": "e56b5b2b-40d4-491f-bab2-ecb96bc911a7",
      "developerId": "mBdwaN3QWmmvZsNYuWPdLnzIbaDM",
      "createdAt": "2024-07-22T14:30:04.367Z",
      "type": "WEBHOOK_URL_VERIFICATION",
      "data": {
        "challenge": "8eaacf92948128c760d0a25af59231693b2f930fcf8bad61d780fa2796d923bec5bc64e6da55f17196b10a10850f0fa2"
      }
    }
    ```

    **Verification Response Example:**

    ```json
    {
      "challenge": "8eaacf92948128c760d0a25af59231693b2f930fcf8bad61d780fa2796d923bec5bc64e6da55f17196b10a10850f0fa2"
    }
    ```
  </Step>

  <Step title="Verify Functionality">
    Follow our [webhook testing guide](/test-webhooks-integration) to ensure your endpoint correctly receives and processes webhooks.
  </Step>

  <Step title="Implement Signature Verification">
    Each webhook includes a cryptographic signature to verify its authenticity. [Verifying this signature](#webhook-verification) is essential for security.
  </Step>
</Steps>

Once set up, your endpoint will receive a webhook for every relevant event, such as order updates.

## Webhook Payload

Rye webhooks follow a consistent structure:

```typescript
interface BaseWebhook {
  /** Unique ID for this webhook */
  id: string;
  /** Originating request ID */
  requestId: string;
  /** Your developer ID; matches the `Rye-Verification` header */
  developerId: string;
  /** Event type */
  type: string;
  /** Event-specific data */
  data: object;
  /** Event creation timestamp in ISO format */
  createdAt: string;
}
```

For a complete list of webhook event types, visit our [webhooks events page](/webhooks/events).

## Webhook Verification

### Security Measures

* **Signature Validation:** Each webhook includes a `Rye-Hmac-Signature-V1` header to verify authenticity.
* **Challenge Response:** Your endpoint must respond to the initial `challenge` during setup.

### Headers Overview

| Header                  | Description                                                                                                                                                      |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Rye-Hmac-Signature-V1` | Cryptographic signature to verify the webhook's authenticity. Refer to the [verification section](#verifying-the-signature-and-handling-challenges) for details. |
| `Rye-Verification`      | Your developer account ID, useful for endpoints handling multiple Rye accounts.                                                                                  |

<Warning>
  **CORS Configuration:** Ensure your backend allows Rye's headers by updating your CORS policy accordingly.
</Warning>

### Verifying the Signature and Handling Challenges

1. **Compute HMAC:**
   * Use SHA-256 with your HMAC secret key to hash the request body.
   * Base64-encode the resulting digest.

2. **Compare Signatures:**
   * Match the computed signature with the `Rye-Hmac-Signature-V1` header.
   * Reject the request if they do not match.

3. **Handle Challenge:**
   * If the payload includes a `challenge`, respond with its value to complete verification.

**Pseudocode Example:**

```plaintext
cipher = new_hmac(algorithm: "sha256", secret: "<<your hmac secret>>")
signature = base64encode(cipher.hash_data(request.body))
```

Your HMAC secret key is available in the Rye Console on the [Account page](https://console.rye.com/account) under the Webhooks section.

### Testing Webhooks Locally

To test webhooks locally, you can use [ngrok](https://ngrok.com/docs/getting-started/) to expose your local server to the internet. For example, run the following command to create a public URL that forwards to your local webhook endpoint:

```bash
ngrok http 3000
```

Ensure the `RYE_HMAC_SECRET_KEY` environment variable is set in your application.

<Tabs items={["webhook.js", "webhook.go", "webhook.py"]}>
  <Tab title="webhook.js">
    ```javascript
    // npm install express body-parser crypto
    const crypto = require('crypto');
    const express = require('express');
    const bodyParser = require('body-parser');

    const app = express();
    const SECRET_KEY = process.env.RYE_HMAC_SECRET_KEY;

    // Parse raw body for signature verification
    app.use(bodyParser.raw({ type: '*/*' }));

    app.post('/webhook', (req, res) => {
      const receivedSignature = req.headers['rye-hmac-signature-v1'];
      const hmac = crypto.createHmac('sha256', SECRET_KEY);
      hmac.update(req.body);
      const expectedSignature = hmac.digest('base64');

      if (expectedSignature !== receivedSignature) {
        return res.status(401).send('Unauthorized');
      }

      const data = JSON.parse(req.body);

      if (data.data?.challenge) {
        return res.json({ challenge: data.data.challenge });
      }

      // Process the webhook event
      res.status(200).send('Event received');
    });

    // Start the server
    app.listen(3000, () => {
      console.log('Server listening on port 3000');
    });
    ```
  </Tab>

  <Tab title="webhook.go">
    ```go
    // go get crypto/hmac crypto/sha256 encoding/base64 encoding/json net/http
    package main

    import (
    	"crypto/hmac"
    	"crypto/sha256"
    	"encoding/base64"
    	"encoding/json"
    	"io/ioutil"
    	"log"
    	"net/http"
    	"os"
    )

    var SECRET_KEY string

    func main() {
    	SECRET_KEY = os.Getenv("RYE_HMAC_SECRET_KEY")
    	if SECRET_KEY == "" {
    		log.Fatal("RYE_HMAC_SECRET_KEY not set")
    	}

    	http.HandleFunc("/webhook", webhookHandler)
    	log.Fatal(http.ListenAndServe(":3000", nil))
    }

    func webhookHandler(w http.ResponseWriter, r *http.Request) {
    	ryeSignature := r.Header.Get("Rye-Hmac-Signature-V1")
    	if ryeSignature == "" {
    		http.Error(w, "Missing signature", http.StatusBadRequest)
    		return
    	}

    	body, err := ioutil.ReadAll(r.Body)
    	if err != nil {
    		http.Error(w, "Error reading body", http.StatusInternalServerError)
    		return
    	}
    	defer r.Body.Close()

    	mac := hmac.New(sha256.New, []byte(SECRET_KEY))
    	mac.Write(body)
    	expectedSignature := base64.StdEncoding.EncodeToString(mac.Sum(nil))

    	if !hmac.Equal([]byte(expectedSignature), []byte(ryeSignature)) {
    		http.Error(w, "Forbidden", http.StatusForbidden)
    		return
    	}

    	var data map[string]interface{}
    	if err := json.Unmarshal(body, &data); err != nil {
    		http.Error(w, "Invalid JSON", http.StatusBadRequest)
    		return
    	}

    	if dataData, ok := data["data"].(map[string]interface{}); ok {
    		if challenge, ok := dataData["challenge"].(string); ok {
    			response := map[string]string{"challenge": challenge}
    			w.Header().Set("Content-Type", "application/json")
    			json.NewEncoder(w).Encode(response)
    			return
    		}
    	}

    	// Process the webhook event
    	w.WriteHeader(http.StatusOK)
    	w.Write([]byte("Event received"))
    }
    ```
  </Tab>

  <Tab title="webhook.py">
    ```python
    # pip install flask
    from flask import Flask, request, jsonify
    import os
    import hmac
    import hashlib
    import base64
    import json

    app = Flask(__name__)
    SECRET_KEY = os.getenv('RYE_HMAC_SECRET_KEY')

    @app.route('/webhook', methods=['POST'])
    def webhook():
        received_signature = request.headers.get('Rye-Hmac-Signature-V1')
        if not received_signature:
            return 'Missing signature', 400

        body = request.get_data()
        hmac_obj = hmac.new(SECRET_KEY.encode(), body, hashlib.sha256)
        expected_signature = base64.b64encode(hmac_obj.digest()).decode()

        if not hmac.compare_digest(received_signature, expected_signature):
            return 'Unauthorized', 401

        data = request.get_json()
        challenge = data.get('data', {}).get('challenge')
        if challenge:
            return jsonify({'challenge': challenge})

        # Process the webhook event
        return 'Event received', 200

    if __name__ == '__main__':
        app.run(port=3000)
    ```
  </Tab>
</Tabs>

## Caveats

* **Reliable Delivery:** Rye will attempt to deliver each webhook twice. Ensure your endpoint reliably handles requests without errors.
* **Timeouts:** Webhook requests must respond within **10 seconds**. Longer processing may result in missed events.
* **Event Ordering:** Webhooks may arrive out of order. Use the `createdAt` timestamp to sequence events appropriately in your system.


---

*Powered by [holocron.so](https://holocron.so)*
