> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.deel.wtf/api/stable/webhooks/quickstart/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.deel.wtf/_mcp/server. # Quickstart > Set up your first Deel webhook in minutes > **Info** > > **Not a developer?** You can set up webhooks without writing any code using the [Deel Developer Center](/api/webhooks/no-code). This guide is for developers who want to integrate webhooks programmatically. ## What You'll Learn This quickstart guide will walk you through: 1. Creating a webhook endpoint to receive events 2. Verifying webhook signatures for security 3. Subscribing to specific events 4. Testing your webhook integration > **Note** > > **Time to complete:** \~15 minutes ## Prerequisites Before you begin, ensure you have: * **API Token** – Get your API token from the Deel Developer Center * **Development Environment** – Node.js, Python, Go, or PHP installed locally * **Testing Tool** – ngrok or a similar tool to expose localhost ## Step 1: Create a Webhook Endpoint First, create a simple HTTP endpoint that can receive POST requests from Deel. > **Tip** > > **Choose your language:** We'll show examples in multiple languages. Pick the one you're most comfortable with! **`Node.js`** ```javascript Node.js // webhook-server.js const express = require('express'); const crypto = require('crypto'); const app = express(); // Middleware to capture raw body for signature verification app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf.toString(); } })); // Webhook endpoint app.post('/webhooks/deel', (req, res) => { console.log('✅ Webhook received!'); console.log('Event type:', req.body.data?.meta?.event_type); // Always respond with 200 immediately res.status(200).send('OK'); }); app.listen(3000, () => { console.log('🚀 Webhook server running on http://localhost:3000'); }); ``` **`Python`** ```python Python # webhook_server.py from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/webhooks/deel', methods=['POST']) def handle_webhook(): print('✅ Webhook received!') event = request.json print(f"Event type: {event['data']['meta']['event_type']}") # Always respond with 200 immediately return jsonify({'status': 'success'}), 200 if __name__ == '__main__': app.run(port=3000, debug=True) ``` **`Go`** ```go Go // webhook_server.go package main import ( "encoding/json" "fmt" "log" "net/http" ) type WebhookPayload struct { Data struct { Meta struct { EventType string `json:"event_type"` } `json:"meta"` } `json:"data"` } func handleWebhook(w http.ResponseWriter, r *http.Request) { fmt.Println("✅ Webhook received!") var payload WebhookPayload json.NewDecoder(r.Body).Decode(&payload) fmt.Printf("Event type: %s\n", payload.Data.Meta.EventType) // Always respond with 200 immediately w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) } func main() { http.HandleFunc("/webhooks/deel", handleWebhook) fmt.Println("🚀 Webhook server running on http://localhost:3000") log.Fatal(http.ListenAndServe(":3000", nil)) } ``` **`PHP`** ```php PHP ``` > **Note** > > **Start your server** and make sure it's running on port 3000. We'll expose it publicly in the next step. ## Step 2: Expose Your Local Server Use ngrok to create a public HTTPS URL for your local server: #### Install ngrok Download and install ngrok from [ngrok.com](https://ngrok.com) ```bash # Or install via homebrew (macOS) brew install ngrok ``` #### Start ngrok tunnel Run ngrok to expose port 3000: ```bash ngrok http 3000 ``` You'll see output like: ``` Forwarding https://abc123.ngrok.io -> http://localhost:3000 ``` #### Copy your HTTPS URL Copy the `https://` URL (e.g., `https://abc123.ngrok.io`) Your webhook URL will be: `https://abc123.ngrok.io/webhooks/deel` > **Warning** > > **Keep ngrok running** in a separate terminal window while testing. If you restart ngrok, you'll get a new URL and need to update your webhook subscription. ## Step 3: Create a Webhook Subscription Now subscribe to webhook events using the Deel API. > **Tip** > > **Prefer a visual interface?** You can also create webhooks through the [Deel Developer Center](/api/webhooks/no-code) without writing code. This guide uses the API for learning purposes. **`cURL`** ```bash cURL curl -X POST 'https://api.letsdeel.com/rest/webhooks' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "My first webhook", "description": "My first webhook", "status": "enabled", "url": "https://abc123.ngrok.io/webhooks/deel", "api_version": "v2", "events": ["contract.created", "contract.signed"] }' ``` **`Node.js`** ```javascript Node.js const axios = require('axios'); async function createWebhook() { const response = await axios.post( 'https://api.letsdeel.com/rest/webhooks', { name: 'My first webhook', description: 'My first webhook', status: 'enabled', url: 'https://abc123.ngrok.io/webhooks/deel', api_version: 'v2', events: ['contract.created', 'contract.signed'] }, { headers: { 'Authorization': `Bearer ${process.env.DEEL_API_TOKEN}`, 'Content-Type': 'application/json' } } ); console.log('Webhook created:', response.data.data); console.log('Webhook ID:', response.data.data.id); console.log('Signing key:', response.data.data.signing_key); // Save this! } createWebhook(); ``` **`Python`** ```python Python import requests import os def create_webhook(): response = requests.post( 'https://api.letsdeel.com/rest/webhooks', json={ 'name': 'My first webhook', 'description': 'My first webhook', 'status': 'enabled', 'url': 'https://abc123.ngrok.io/webhooks/deel', 'api_version': 'v2', 'events': ['contract.created', 'contract.signed'] }, headers={ 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}', 'Content-Type': 'application/json' } ) data = response.json()['data'] print(f'Webhook created: {data}') print(f'Webhook ID: {data["id"]}') print(f'Signing key: {data["signing_key"]}') # Save this! create_webhook() ``` > **Warning** > > **Save the signing key!** The API response includes a `signing_key` that you'll need for signature verification in the next step. Store it securely as an environment variable. > **Tip** > > **Don't know which events to subscribe to?** List all available events first: > > ```bash > curl 'https://api.letsdeel.com/rest/webhooks/events/types' \ > -H 'Authorization: Bearer YOUR_API_TOKEN' > ``` ## Step 4: Add Signature Verification Now add security to your webhook endpoint by verifying signatures: **`Node.js`** ```javascript Node.js // webhook-server.js const express = require('express'); const crypto = require('crypto'); const app = express(); const SIGNING_KEY = process.env.DEEL_WEBHOOK_SECRET; // From Step 3 app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf.toString(); } })); function verifySignature(req) { const signature = req.headers['x-deel-signature']; const expectedSignature = crypto .createHmac('sha256', SIGNING_KEY) .update('POST' + req.rawBody) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } app.post('/webhooks/deel', (req, res) => { // Verify signature if (!verifySignature(req)) { console.log('❌ Invalid signature!'); return res.status(401).send('Invalid signature'); } console.log('✅ Signature verified!'); console.log('Event:', req.body.data.meta.event_type); res.status(200).send('OK'); }); app.listen(3000, () => { console.log('🚀 Secure webhook server running'); }); ``` **`Python`** ```python Python # webhook_server.py from flask import Flask, request, jsonify import hmac import hashlib import os app = Flask(__name__) SIGNING_KEY = os.getenv('DEEL_WEBHOOK_SECRET') # From Step 3 def verify_signature(request): signature = request.headers.get('x-deel-signature') raw_body = request.get_data(as_text=True) expected_signature = hmac.new( SIGNING_KEY.encode(), ('POST' + raw_body).encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected_signature) @app.route('/webhooks/deel', methods=['POST']) def handle_webhook(): # Verify signature if not verify_signature(request): print('❌ Invalid signature!') return jsonify({'error': 'Invalid signature'}), 401 print('✅ Signature verified!') event = request.json print(f"Event: {event['data']['meta']['event_type']}") return jsonify({'status': 'success'}), 200 if __name__ == '__main__': app.run(port=3000, debug=True) ``` > **Note** > > **Security tip:** The signature is computed as `HMAC-SHA256(signing_key, "POST" + raw_body)`. Always prefix with "POST" before hashing! ## Step 5: Test It Out! Now trigger a webhook event to see everything working: #### Ensure everything is running Make sure you have: * ✅ Your webhook server running on port 3000 * ✅ ngrok tunnel active * ✅ Webhook subscription created #### Trigger an event in Deel Sandbox Log into your [Deel Sandbox](https://app-sandbox.letsdeel.com) and perform an action that triggers your subscribed event: * Create a new contract (triggers `contract.created`) * Sign a contract (triggers `contract.signed`) * Complete a payment (triggers `payment.completed`) #### Check your server logs You should see output like: ``` ✅ Signature verified! Event: contract.created ``` #### Verify in ngrok dashboard Open the ngrok web interface at [http://localhost:4040](http://localhost:4040) to see the webhook request details > **Tip** > > **Webhook not arriving?** Deel retries failed deliveries with exponential backoff. If your endpoint was down, the webhook will be retried automatically up to 10 times. ## What's in a Webhook Payload? Here's what you'll receive when an event occurs: ```json { "data": { "meta": { "event_type": "contract.created", "organization_id": "c2f26732-e747-4776-8a21-b31c379f2356" }, "resource": [ { "contract_id": "123abc1", "worker_email": "contractor@example.com", "status": "pending", "created_at": "2025-02-05T15:39:38.070Z" } ] }, "timestamp": "2025-02-05T15:39:38.070Z" } ``` **Key fields:** * `data.meta.event_type` - The type of event that occurred * `data.meta.organization_id` - Your organization ID * `data.resource` - Event-specific data (varies by event type) * `timestamp` - When the event occurred (ISO 8601 format) ## Common Issues & Solutions #### Not receiving any webhooks **Check:** * Is your server running and accessible via ngrok? * Did you use the correct ngrok HTTPS URL when creating the subscription? * Are you triggering the right events you subscribed to? **Quick test:** ```bash curl -X POST https://abc123.ngrok.io/webhooks/deel \ -H "Content-Type: application/json" \ -d '{"test": "data"}' ``` #### Signature verification failing **Common mistakes:** * Not prefixing with "POST" before hashing * Using parsed JSON instead of raw body * Wrong signing key (check your environment variables) **Debug it:** ```javascript console.log('Received signature:', req.headers['x-deel-signature']); console.log('Raw body:', req.rawBody); console.log('Signing key:', SIGNING_KEY); ``` #### Getting 401 or 403 errors when creating webhook **Possible causes:** * Invalid or expired API token * Using sandbox token for production API (or vice versa) **Solution:** Verify your token: ```bash curl 'https://api.letsdeel.com/rest/organizations' \ -H 'Authorization: Bearer YOUR_API_TOKEN' ``` ## Learn More #### Managing Webhooks You can manage webhook subscriptions via API or through the Developer Center: **Via API:** * `GET /webhooks` - List all your webhooks * `GET /webhooks/{id}` - Get webhook details * `PATCH /webhooks/{id}` - Update webhook URL or events * `DELETE /webhooks/{id}` - Delete webhook **Via Developer Center:** Visit the [Managing Webhooks](/api/webhooks/no-code) guide to learn how to create, view, edit, test, and monitor webhooks through the visual interface. #### Webhook Retry Behavior Deel retries failed webhooks up to 10 times with exponential backoff: * 1st retry: 1 minute * 2nd retry: 2 minutes * 3rd retry: 4 minutes * 4th-9th retries: Continue with exponential backoff * 10th attempt: After 16 hours, webhook is disabled if it fails After 10 failures, the webhook is automatically disabled and you'll need to re-enable it. #### Production Best Practices Before going live, make sure you: * Always verify signatures with constant-time comparison * Respond within 30 seconds (ideally under 5 seconds) * Use HTTPS with a valid SSL certificate * Log webhook deliveries for debugging * Monitor for failures and retries * Implement graceful error handling * Set up alerts for webhook failures ## Additional Resources #### [Events](/api/webhooks/events) Learn about webhook events #### [Simulations](/api/webhooks/simulations) Simulate webhook events #### [Endpoints](/api/endpoints/webhooks) Webhook endpoints > Build apps and integrations that extend and enhance the Deel services.