{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-web3/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["tabs","tab"]},"type":"markdown"},"seo":{"title":"Webhooks in the Partner API","description":"The developer documentation portal and API reference for Unstoppable Domains.","siteUrl":"https://docs.unstoppabledomains.com","keywords":"unstoppable domains developer portal, api reference docs","lang":"en-US","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"webhooks-in-the-partner-api","__idx":0},"children":["Webhooks in the Partner API"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The Partner API offers webhooks as a way to register asynchronous callbacks from our servers to yours when important events take place. This provides the most efficient way for your application to handle updates from the Partner API without needing to poll for updates."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-are-webhooks","__idx":1},"children":["What are Webhooks?"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A webhook is an HTTP-based callback that your web server receives from our servers. This means our servers will make an HTTP API call to your server to notify your application that a specific event has taken place."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Webhooks enable applications to be written with reactive, asynchronous logic instead of using a proactive, synchronous polling approach. This is the most efficient way for applications to be implemented to not waste resources checking for updates when there are none to receive."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"registering-webhooks","__idx":2},"children":["Registering Webhooks"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The Partner API provides ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://docs.unstoppabledomains.com/apis/partner/latest/#tag/webhooks"},"children":["several endpoints for managing webhooks"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You will need an absolute URL to your server where we should send webhook requests. For example: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://api.my-server.com/webhooks"]},". This endpoint should be setup to receive ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST"]}," requests with an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["application/json"]}," Content Type."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Once you know the URL for your server, you simply need to make a ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://docs.unstoppabledomains.com/apis/partner/latest/#operation/createWebhook"},"children":["registration request"]}," using that URL and the desired webhook event type. In the following example payload we are using the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OPERATION_FINISHED"]}," webhook event type."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"url\": \"https://api.my-server.com/webhooks\",\n  \"type\": \"OPERATION_FINISHED\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["That's it! Now your server will receive ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST"]}," requests anytime an operation completes."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"receiving-webhooks","__idx":3},"children":["Receiving Webhooks"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your server should be prepared to receive HTTP ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST"]}," requests that have an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["application/json"]}," body. The request to your server will include a JSON payload and headers from our server that you can use to process the update. The request body will depend on the webhook event type. In the example from the previous section, we registered an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OPERATION_FINISHED"]}," webhook. For the exact request to expect, ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://docs.unstoppabledomains.com/apis/partner/latest/#operation/webhook_OperationFinished"},"children":["see the API specification"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Other considerations:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["In addition to processing the JSON payload, you should check the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-ud-timestamp"]}," header to ensure you are not receiving updates out of order."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Your application needs to respond with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200"]}," status code to confirm it successfully received the request. Any other response status will result in us retrying delivery of the webhook."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Here is a simple example of a webhook-receiving server using Node and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["express"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import * as express from 'express';\nconst app = express();\n\n// Setup express to receive JSON payloads\napp.use(express.json());\n\n// Receive the webhook at api.my-server.com/webhooks\napp.post('/webhooks', (req, res) => {\n  const { body } = req;\n  const headers = Object.fromEntries(\n      Object.entries(req.headers).filter(\n        ([key]) =>\n          key.toLowerCase().startsWith('x-ud-')\n      ),\n    );\n\n  // Log the headers and body\n  console.log(\n    'Received Webhook\\nHEADERS:\\n',\n    JSON.stringify(headers, null, 2),\n    '\\nBODY:\\n',\n    JSON.stringify(body, null, 2),\n  );\n\n  // Send successful response to the UD server\n  res.sendStatus(200);\n});\n\nconst port = process.env.PORT ?? 3000;\napp.listen(port, () => {\n  console.log('Started webhook dev server on port', port);\n});\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"webhook-delivery-retries","__idx":4},"children":["Webhook Delivery Retries"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If your application does not respond with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200"]}," status code, we will attempt to retry the delivery up to 8 times."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The retry delay interval doubles after each failed attempt, resulting in the following retry schedule:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["1 minute delay"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["2 minutes"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["4 minutes"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["8 minutes"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["16 minutes"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["32 minutes"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["64 minutes"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["120 minutes (2 hours)"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If your server fails to respond successfully after these retries we will no longer attempt to delivery that payload and you must use the ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://docs.unstoppabledomains.com/apis/partner/latest/#operation/checkOperation"},"children":["API to check for the expected updates"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"verifying-webhooks","__idx":5},"children":["Verifying Webhooks"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The webhook requests sent to your server do not include traditional authentication headers, instead it includes a signature header (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-ud-signature"]},") that should be used to verify the authenticity of the request."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-ud-signature"]}," header is a Base64 encoded HMAC-SHA256 of the raw payload body bytes, using your account's primary API key as the secret."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You simply need to recompute the HMAC in your application to verify the request:"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"TypeScript (Node)","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import { createHmac } from 'crypto';\n\nfunction verifyRequest(signatureHeader: string, rawBodyBytes: Buffer, accountApiKey: string): boolean {\n    const computedSignature = createHmac('sha256', accountApiKey)\n        .update(rawBodyBytes)\n        .digest('base64');\n    return computedSignature === signatureHeader;\n}\n","lang":"typescript"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import hashlib\nimport hmac\nimport base64\n\ndef verify_request(signature_header, raw_body_bytes, account_api_key):\n    computed_hmac = hmac.new(account_api_key.encode(), raw_body_bytes, hashlib.sha256).digest()\n    computed_signature = base64.b64encode(computed_hmac).decode()\n    return computed_signature == signature_header\n","lang":"python"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"Go","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"go","header":{"controls":{"copy":{}}},"source":"import (\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/base64\"\n)\n\nfunc verifyRequest(signatureHeader string, rawBodyBytes []byte, accountAPIKey string) bool {\n    key := []byte(accountAPIKey)\n    mac := hmac.New(sha256.New, key)\n    mac.Write(rawBodyBytes)\n    expectedMAC := mac.Sum(nil)\n    computedSignature := base64.StdEncoding.EncodeToString(expectedMAC)\n    return computedSignature == signatureHeader\n}\n","lang":"go"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"Java","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"import javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.nio.charset.StandardCharsets;\nimport java.security.InvalidKeyException;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.Base64;\n\n// Should be in a class...\n\npublic static boolean verifyRequest(String signatureHeader, byte[] rawBodyBytes, String accountApiKey) {\n    try {\n        Mac mac = Mac.getInstance(\"HmacSHA256\");\n        SecretKeySpec secretKey = new SecretKeySpec(accountApiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\");\n        mac.init(secretKey);\n        byte[] computedSignature = mac.doFinal(rawBodyBytes);\n        String encodedComputedSignature = Base64.getEncoder().encodeToString(computedSignature);\n        return encodedComputedSignature.equals(signatureHeader);\n    } catch (NoSuchAlgorithmException | InvalidKeyException e) {\n        e.printStackTrace();\n    }\n    return false;\n}\n","lang":"java"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"PHP","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"php","header":{"controls":{"copy":{}}},"source":"function verifyRequest($signatureHeader, $rawBodyBytes, $accountApiKey) {\n    $computedSignature = base64_encode(hash_hmac('sha256', $rawBodyBytes, $accountApiKey, true));\n    return $computedSignature === $signatureHeader;\n}\n","lang":"php"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"C#","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"using System;\nusing System.Security.Cryptography;\nusing System.Text;\n\n// Should be in a class...\n\npublic static bool VerifyRequest(string signatureHeader, byte[] rawBodyBytes, string accountApiKey)\n{\n    using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(accountApiKey)))\n    {\n        byte[] computedSignature = hmac.ComputeHash(rawBodyBytes);\n        string encodedComputedSignature = Convert.ToBase64String(computedSignature);\n        return encodedComputedSignature == signatureHeader;\n    }\n}\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When using Node with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["express"]},", you can use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["express.json({ verify: ... })"]}," callback to get access to the raw buffer:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import * as express from 'express';\nconst app = express();\n\napp.use(\n  express.json({\n    verify(req, _res, buf) {\n      // Attach the buffer to the request object so you can handle the verification in your route handler\n      (req as unknown as Record<string, unknown>).rawBodyBuffer = buf;\n\n      // OR verify the request here\n      const signature = req.headers['x-signature-header']?.toString();\n      if(!signature || !verifyRequest(signature, buf, process.env.UD_PARTNER_API_KEY)) {\n        throw new Error('Not authentic!');\n      }\n    },\n  }),\n);\n","lang":"typescript"},"children":[]}]},"headings":[{"value":"Webhooks in the Partner API","id":"webhooks-in-the-partner-api","depth":1},{"value":"What are Webhooks?","id":"what-are-webhooks","depth":2},{"value":"Registering Webhooks","id":"registering-webhooks","depth":2},{"value":"Receiving Webhooks","id":"receiving-webhooks","depth":2},{"value":"Webhook Delivery Retries","id":"webhook-delivery-retries","depth":3},{"value":"Verifying Webhooks","id":"verifying-webhooks","depth":2}],"frontmatter":{"title":"Implementing Webhooks with the Partner API | Unstoppable Domains Developer Portal","description":"How to create, receive and verify webhooks when using the Unstoppable Domains Partner API","seo":{"title":"Webhooks in the Partner API"}},"editPage":{"to":"https://github.com/unstoppabledomains/dev-docs/blob/main/web3/domain-distribution-and-management/guides/implementing-webhooks.md"},"lastModified":"2026-04-10T16:45:57.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/web3/domain-distribution-and-management/guides/implementing-webhooks","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}