Webhook request
Authentication and Processing
Webhooks sent from Extend require digital signature verification. Once verified, the webhooks can be processed for business logic.
- The webhook payload is JSON.
- The signature is sent as a
signature
header. - Extend's public key id is sent as the
X-Extend-Key-Id
header. - Extend's key id is also sent in the webhook request payload as
kid
. Confirm that the value ofX-Extend-Key-Id
andkid
match.
As part of the verification flow, you will need to make an HTTP GET request to Extend's JWKS endpoint: https://api.helloextend.com/service-orders/oauth/keys
{{API_HOST}}/service-orders/oauth/keys
{
"keys": [
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"key_ops": [
"sign",
"verify"
],
"kid": "b35edb9f-4be9-4cb4-9425-6f3d04ac9347",
"n": "tvFPcO-lSUcW9n_dv45wfY0yF47CXZLICqS5kWIypfpP2NX4WRDK8c0bXx6g9P4n_UjJqyDsa-ocMil2dWRDn2oGCzrZLvk6bwerQfCij0Qvy92YZAWJ8r2QrQN69LZzrzCqpMbleclb4du5L46tIdWpOuWx9JWOD_F_HROyd26-SIUvrsLnk7pjSsRx1Bp9mw-qjIB2tEFCINrD3IDcFicPKMpWImhNA-Bukq3ZySzq2VvqAqmhif5EsLw4XWXOucM7YWLn5MfscRippwky94emVD4Hc3LaIYdjxcSMd9Gyr4vgEQU-sgj_hrBfjCabLEsfELIbrC4aPaNN1VTar91ErycEtmH5fNAhI7ka1YUFt5-AgvXDcPs4vnHuTX86TG_0IMhSxr-jvdarYJbTdbLU2-ViRSjIc3FSg4qjvxmyfJNXkwIWHy5Mh3kEK3GA573GofyCTjo20IwjNwhys0PdMnC9YXJQATPE4BvADaaA4UiKDeMfpOXA4Y6dgA9HDRVoA0RgNAycfHh0eIyjnOZjaU7napAxQjr7xqTw_8d8_MpCdAaAb70iL5pbbAtXpuhB27fvKNgvQGD3-X6idEbMYuJmZHzqDMfEmJROd4SjQl0OR5Lay5KpZyvjVsskh0GEbU_HR87UYP8FJn-avDhG6jU-HtSDJahACXeaS-s",
"e": "AQAB"
}
]
}
- Parse the JSON response which contains an array of keys in JWK format. Find
the specific key that matches the
kid
from the webhook payload. - Convert the JWK (JSON Web Key) to PEM format, which is required for most
signature verification libraries. This typically involves using a crypto
library to create a public key object from the JWK and exporting it in PEM
format. Here is an example using the
crypto
Node module.
/**
* Convert a JWK to PEM format
*
* @param jwk - The JWK object (the full key Object from the 'keys' array)
* @returns PEM formatted key
*/
function jwkToPem(jwk: any): string {
try {
const publicKey = crypto.createPublicKey({
key: jwk as unknown as NodeJsonWebKey,
format: 'jwk',
})
return publicKey.export({ type: 'spki', format: 'pem' }).toString()
} catch (error) {
Log.error('Error converting JWK to PEM', error)
throw error
}
}
Verify the Digital Signature. Use a cryptographic library to verify that the signature was created using the private key corresponding to the public key. The verification should use the RSA-SHA256 algorithm. The signature is typically base64-encoded and needs to be decoded before verification.
import crypto from 'crypto'
/**
* Returns true if signature verification passed
*
* @param body - Stringified body of the webhook
* @param publicKey - Base64 encoded publicKey.
* @param signature - Base64 encoded signature. Found in webhook ['signature'] header.
* The signature found in the request header is Base64 encoded.
* @returns true if the verification passed
*/
/**
* Verify webhook signature using JWKS approach
*
* @param body - Stringified body of the webhook
* @param signature - Base64 encoded signature from 'signature' header
* @param keyId - Key ID from 'X-Extend-Key-Id' header (should match kid in payload)
* @returns true if the verification passed
*/
async function verifyWebhookSignature(
body: string,
signature: string,
keyId: string,
): Promise<boolean> {
try {
// 1. Fetch JWKS from Extend
const jwksResponse = await fetch(
'https://api.helloextend.com/service-orders/oauth/keys',
)
const jwks = await jwksResponse.json()
// 2. Find the key that matches the keyId
const key = jwks.keys.find((k: any) => k.kid === keyId)
if (!key) {
console.error('Key not found for kid:', keyId)
return false
}
// 3. Convert JWK to PEM
const publicKey = crypto.createPublicKey({
key: key,
format: 'jwk',
})
const pemKey = publicKey.export({ type: 'spki', format: 'pem' })
// 4. Verify signature
return crypto.verify(
'RSA-SHA256',
Buffer.from(body),
pemKey,
Buffer.from(signature, 'base64'),
)
} catch (e) {
console.error('Signature verification failed:', e)
return false
}
}
// Example usage - JWKS approach (recommended)
const requestBody =
'{"serviceOrderId":"73752710-8cdf-4fe3-8327-6936fa3badd6","claimId":"7b67180d-0ae6-4d81-a0ca-d7eeda41870e","contractId":"770e5f9d-fb2d-4c53-9137-cbb647d037f5","servicerId":"26db19a3-d9db-4ad4-9575-b7ca2f6e990a","customerName":"Paul Name","customerEmail":"[email protected]","contractPurchaseDate":1655668093818,"customerShippingAddress":{"city":"San Francisco","address1":"533 Mission Street","provinceCode":"CA","countryCode":"USA","postalCode":"94105"},"customerPhoneNumber":"555-535-5555","productDetails":[{"productReferenceId":"PRODUCT-REPAIR-385UmuLNaUqVmpohrmnvMqrncttQCz","productListPrice":25199,"productPurchasePrice":25199}],"failureType":"webhook parse","failureDescription":"webhook","incident":{"failureType":"electricalFailure","description":"BROKE PLZ HALP","occurredAt":1695152893819,"FailureType":"webhook parse","FailureDescription":"webhook","productCondition":"nonfunctional"},"sendDate":1695152952396,"url":"https://webhook.site/70a09585-f934-4b01-855b-0869e5e19752","transactionId":"63815176-f685-44ca-8180-fb13e39d5d04","type":"assign","kid":"b35edb9f-4be9-4cb4-9425-6f3d04ac9347"}'
const signature =
'JJbRyjmmylP4GJtNCT7wANYVMkmUYENuX13sXqMjbAUcfidBBix/hzCjnoycg6lCT1Ktny2OnmixEHQAwNFOuReTlid+AFvCQnQ0nA+aUjvD3JSYCLLE2SOuvbZ7/Q/fQZNnFrDUX6MXcYkuoNLUmKRagG5uq0UyIEtWgfyLtGJQlZ91KXJkvZsFQu+AO5dFiDceJA+IW11sCN5e/9gKWuHxzxwKepvA8S0lyPwQWYqk1V/O/bV5s5uD5zrI6Wndd8Yq7ejYsWd6ZeHbjrcTbbr35lKsTcpoLxtBjD+H/1X5+pYAPlW7ymLWpPxEAug8tnpzy5HVMtwNZ3br8S5afC9BgR7abGcfa3PjyYvM1tk6+JfCPLa9gIJShu1+eXTq0GUQnxu/0bL/ihnwadHlFZyLVzmRjBkPrsuQsM6KTvXS5Gw+BGo+zy/p7iFUFHws1r6zZNONe0ZxTudpDgGN7IoXmvyj7OOJ7VyGROggB0Ptu2iiNz81uTT1M9z+QCR2pa9E+a6fTWTZIXP86acb8HuKDb8Rd9wkU4G609aZc3QHX0ANKIFSWqhz9WDh0XZgT8daMuda+TmtI4NkTS7j9yQLqvbYQ1W2RpCKVBLgXCgiylBoHfRWFZ/dlOdkojBWaFnWa8ytU69IHFgVAd4V4RVPgjNyWgwovtQtiAvpT3o='
const keyId = 'b35edb9f-4be9-4cb4-9425-6f3d04ac9347' // From X-Extend-Key-Id header
// Verify using modern approach
const isValid = await verifyWebhookSignature(requestBody, signature, keyId)
if (isValid) {
console.log('Signature is valid!')
} else {
console.log('Signature is not valid.')
}
If the signature verification succeeds; process the webhook. If verification fails; reject the webhook.
Complete Example
Here's a full example of handling webhooks built with AWS Lambda using the JWKS approach:
import crypto from 'crypto'
import fetch from 'node-fetch'
const lambda = async event => {
const { headers, body } = event
// Extract signature and key ID from headers
const signature = headers['signature']
const keyId = headers['X-Extend-Key-Id']
console.log('webhook received')
if (!signature || !keyId) {
console.log('Webhook missing required headers. Unauthorized')
return { statusCode: 401 }
}
console.log('attempting to verify RSA-SHA256 signature using JWKS')
const verification = await verifyWebhookSignature(body, signature, keyId)
if (!verification) {
console.log('Unable to verify signature. Unauthorized')
return { statusCode: 401 }
}
console.log('signature verification passed')
// Parse the webhook payload
const webhookData = JSON.parse(body)
// Handle different webhook types
if (webhookData.type === 'assign') {
console.log(
'Processing assigned webhook for service order:',
webhookData.serviceOrderId,
)
// Process assigned webhook logic here
} else if (webhookData.type === 'return_approved') {
console.log(
'Processing return approved webhook for service order:',
webhookData.serviceOrderId,
)
// Process return approved webhook logic here
}
return { statusCode: 200 }
}
/**
* Verify webhook signature using JWKS approach
*
* @param body - Stringified body of the webhook
* @param signature - Base64 encoded signature from 'signature' header
* @param keyId - Key ID from 'X-Extend-Key-Id' header (should match kid in payload)
* @returns true if the verification passed
*/
async function verifyWebhookSignature(
body: string,
signature: string,
keyId: string,
): Promise<boolean> {
try {
// 1. Fetch JWKS from Extend
const jwksResponse = await fetch(
'https://api.helloextend.com/service-orders/oauth/keys',
)
const jwks = await jwksResponse.json()
// 2. Find the key that matches the keyId
const key = jwks.keys.find((k: any) => k.kid === keyId)
if (!key) {
console.error('Key not found for kid:', keyId)
return false
}
// 3. Convert JWK to PEM
const publicKey = crypto.createPublicKey({
key: key,
format: 'jwk',
})
const pemKey = publicKey.export({ type: 'spki', format: 'pem' })
// 4. Verify signature
return crypto.verify(
'RSA-SHA256',
Buffer.from(body),
pemKey,
Buffer.from(signature, 'base64'),
)
} catch (e) {
console.error('Signature verification failed:', e)
return false
}
}
Webhook Request Body
Webhook Types and Usage
Extend sends two types of webhooks to servicers:
- Assigned Webhook (
type: "assign"
): Sent when a service order is assigned to a servicer for repair/replacement - Return Approved Webhook (
type: "return_approved"
): Sent when a return/refund has been approved and the customer needs to ship the item back
Both webhook types include the kid
field for signature verification and follow
the same authentication process.
Assigned Webhook
interface AssignedWebhook {
contractId: string
contractPurchaseDate: number
productDeliveryDate?: number
claimId: string
serviceOrderId: string
serviceAmountAuthorized?: number
orderNumber?: string
customerName: string
customerEmail: string
customerShippingAddress: Address
customerPhoneNumber?: string
productDetails: ProductDetails[]
failureType: string
failureDescription?: string
incident: {
FailureType: string
// Can include additional properties
}
sendDate: number
servicerId: string
url: string
transactionId: string
type: string
kid: string // Key ID for signature verification
}
interface ProductDetails {
productReferenceId: string
sku?: string
productListPrice?: number // In cents
productPurchasePrice?: number // In cents
productVertical?: string
}
interface Address {
address1: string
address2?: string
city: string
countryCode: string
postalCode: string
provinceCode?: string
}
Example
{
"serviceOrderId": "4319c6c7-48aa-4086-a114-b63d9161aa18",
"claimId": "09c6dfc3-3e29-4b3d-a83a-19d9b2c5971a",
"contractId": "15e0abb5-6b96-4f68-827d-ff962027221f",
"servicerId": "3855a17d-3b61-44c9-b45c-43469e9f1d0a",
"customerName": "Customer Name",
"customerEmail": "[email protected]",
"contractPurchaseDate": 1654117627000,
"customerShippingAddress": {
"address2": "Ste 1",
"city": "San Francisco",
"address1": "1 Main St",
"provinceCode": "CA",
"countryCode": "US",
"postalCode": "11111"
},
"customerPhoneNumber": "111-111-1111",
"productDetails": [
{
"productReferenceId": "referenceId",
"productListPrice": 25199,
"productPurchasePrice": 25199
}
],
"failureType": "electricalFailure",
"failureDescription": "The description of the failure.",
"incident": {
"FailureType": "electricalFailure"
},
"sendDate": 1690910629602,
"url": "https://servicer-webhook-url/post-webhook",
"transactionId": "b16a3b34-636c-4941-b69b-8eaf57b62c9f",
"type": "assign",
"kid": "b35edb9f-4be9-4cb4-9425-6f3d04ac9347"
}
Return Approved Webhook
export interface ReturnApprovedWebhook {
claimDetailsUrl: string
orderId: string
orderPurchaseDate?: number
productDeliveryDate?: number
claimId: string
serviceOrderId: string
returnShipment: {
shipmentId: string
carrier: ShipmentCarrier
broker: ShipmentBroker
destination: Address
origin: Address
status: ShipmentStatus
trackingNumber?: string
shippedAt?: number | null
estimatedDeliveryAt?: number | null
deliveredAt?: number | null
}
refundAmount: number
refundOption: ReturnFulfillmentMethod
customerName: string
customerEmail: string
customerShippingAddress: Address
customerPhoneNumber?: string
productDetails: ProductDetails[]
failureType: string
failureDescription?: string
incident: any
sendDate: number
url: string
transactionId: string
kid: string // Key ID for signature verification
}
export interface ProductDetails {
productReferenceId: string
sku?: string
productListPrice?: number
productPurchasePrice?: number
productVertical?: string
}
export interface Address {
address1: string
address2?: string
city: string
countryCode: string
postalCode: string
provinceCode?: string
}
Example
{
"claimDetailsUrl": "merchants.extend.com/store/claims/e18c4150-2bcb-4d80-94af-816f9c59e0e1",
"orderId": "51d1699c-c8f4-4e50-893b-16af8adc994a",
"claimId": "e18c4150-2bcb-4d80-94af-816f9c59e0e1",
"serviceOrderId": "fc47a847-27c8-48c3-a564-68300f5f625b",
"orderPurchaseDate": 1593533340000,
"productDeliveryDate": 1593533340000,
"returnShipment": {
"shipmentId": "6c7278d7-37a5-4253-ba8d-3d266bc68b2a",
"carrier": "ups_ground",
"broker": "ship_engine",
"destination": {
"address2": "Ste 1",
"city": "San Francisco",
"address1": "1 Main St",
"provinceCode": "CA",
"countryCode": "US",
"postalCode": "11111"
},
"origin": {
"address2": "Ste 1",
"city": "San Francisco",
"address1": "1 Main St",
"provinceCode": "CA",
"countryCode": "US",
"postalCode": "11111"
},
"status": "received_by_carrier",
"trackingNumber": "1220488b-0793-47ea-9c9e-ae35cdf66a70",
"shippedAt": 1593533340000,
"estimatedDeliveryAt": 1593993600000
},
"refundAmount": 12999,
"refundOption": "store_credit",
"customerName": "Customer Name",
"customerEmail": "[email protected]",
"customerShippingAddress": {
"address2": "Ste 1",
"city": "San Francisco",
"address1": "1 Main St",
"provinceCode": "CA",
"countryCode": "US",
"postalCode": "11111"
},
"customerPhoneNumber": "111-111-1111",
"productDetails": [
{
"productReferenceId": "referenceId",
"productListPrice": 25199,
"productPurchasePrice": 25199
}
],
"failureType": "electricalFailure",
"failureDescription": "The description of the failure.",
"incident": {
"FailureType": "electricalFailure"
},
"sendDate": 1740766088101,
"url": "https://servicer-webhook-url/post-webhook",
"transactionId": "b16a3b34-636c-4941-b69b-8eaf57b62c9f",
"type": "return_approved",
"kid": "b35edb9f-4be9-4cb4-9425-6f3d04ac9347"
}
Webhooks should be responded to with a status code 200. All requests that do not receive a 200 will be retried every hour for 48 hours.
Legacy Method For Signature Verification
Public Key endpoint
;/ GET /service-orders/public-key
interface ResponseBody {
key: string
}
The public key will be base64 encoded.
Signature
The webhook request will come with a signature header. The webhook signature will be base64 encoded. The signature will be what is used to verify that the message has not been tampered with or altered. Any message where the signature cannot be verified should be discarded. Use a SHA256 signature verification library to verify the message.
An example of how to verify a digital signature.
import crypto from 'crypto'
/**
* Returns true if signature verification passed
*
* @param body - Stringified body of the webhook
* @param publicKey - Base64 encoded publicKey. Public key can be fetched at GET /service-orders/public-key
* The public key endpoint returns the key Base64 encoded.
* @param signature - Base64 encoded signature. Found in webhook ['signature'] header.
* The signature found in the request header is Base64 encoded.
* @returns true if the verification passed
*/
function verifyDigitalSignature(
body: string,
publicKey: string,
signature: string,
): boolean {
try {
return crypto.verify(
'RSA-SHA256',
Buffer.from(body),
Buffer.from(publicKey, 'base64'),
Buffer.from(signature, 'base64'),
)
} catch (e) {
return false
}
}
// Example usage
const publicKey =
'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUF0dkZQY08rbFNVY1c5bi9kdjQ1dwpmWTB5RjQ3Q1haTElDcVM1a1dJeXBmcFAyTlg0V1JESzhjMGJYeDZnOVA0bi9VakpxeURzYStvY01pbDJkV1JECm4yb0dDenJaTHZrNmJ3ZXJRZkNpajBRdnk5MllaQVdKOHIyUXJRTjY5TFp6cnpDcXBNYmxlY2xiNGR1NUw0NnQKSWRXcE91V3g5SldPRC9GL0hST3lkMjYrU0lVdnJzTG5rN3BqU3NSeDFCcDltdytxaklCMnRFRkNJTnJEM0lEYwpGaWNQS01wV0ltaE5BK0J1a3EzWnlTenEyVnZxQXFtaGlmNUVzTHc0WFdYT3VjTTdZV0xuNU1mc2NSaXBwd2t5Cjk0ZW1WRDRIYzNMYUlZZGp4Y1NNZDlHeXI0dmdFUVUrc2dqL2hyQmZqQ2FiTEVzZkVMSWJyQzRhUGFOTjFWVGEKcjkxRXJ5Y0V0bUg1Zk5BaEk3a2ExWVVGdDUrQWd2WERjUHM0dm5IdVRYODZURy8wSU1oU3hyK2p2ZGFyWUpiVApkYkxVMitWaVJTakljM0ZTZzRxanZ4bXlmSk5Ya3dJV0h5NU1oM2tFSzNHQTU3M0dvZnlDVGpvMjBJd2pOd2h5CnMwUGRNbkM5WVhKUUFUUEU0QnZBRGFhQTRVaUtEZU1mcE9YQTRZNmRnQTlIRFJWb0EwUmdOQXljZkhoMGVJeWoKbk9aamFVN25hcEF4UWpyN3hxVHcvOGQ4L01wQ2RBYUFiNzBpTDVwYmJBdFhwdWhCMjdmdktOZ3ZRR0QzK1g2aQpkRWJNWXVKbVpIenFETWZFbUpST2Q0U2pRbDBPUjVMYXk1S3BaeXZqVnNza2gwR0ViVS9IUjg3VVlQOEZKbithCnZEaEc2alUrSHRTREphaEFDWGVhUytzQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ=='
const requestBody =
'{"serviceOrderId":"73752710-8cdf-4fe3-8327-6936fa3badd6","claimId":"7b67180d-0ae6-4d81-a0ca-d7eeda41870e","contractId":"770e5f9d-fb2d-4c53-9137-cbb647d037f5","servicerId":"26db19a3-d9db-4ad4-9575-b7ca2f6e990a","customerName":"Paul Name","customerEmail":"[email protected]","contractPurchaseDate":1655668093818,"customerShippingAddress":{"city":"San Francisco","address1":"533 Mission Street","provinceCode":"CA","countryCode":"USA","postalCode":"94105"},"customerPhoneNumber":"555-535-5555","productDetails":[{"productReferenceId":"PRODUCT-REPAIR-385UmuLNaUqVmpohrmnvMqrncttQCz","productListPrice":25199,"productPurchasePrice":25199}],"failureType":"webhook parse","failureDescription":"webhook","incident":{"failureType":"electricalFailure","description":"BROKE PLZ HALP","occurredAt":1695152893819,"FailureType":"webhook parse","FailureDescription":"webhook","productCondition":"nonfunctional"},"sendDate":1695152952396,"url":"https://webhook.site/70a09585-f934-4b01-855b-0869e5e19752","transactionId":"63815176-f685-44ca-8180-fb13e39d5d04","type":"assign"}'
const signature =
'JJbRyjmmylP4GJtNCT7wANYVMkmUYENuX13sXqMjbAUcfidBBix/hzCjnoycg6lCT1Ktny2OnmixEHQAwNFOuReTlid+AFvCQnQ0nA+aUjvD3JSYCLLE2SOuvbZ7/Q/fQZNnFrDUX6MXcYkuoNLUmKRagG5uq0UyIEtWgfyLtGJQlZ91KXJkvZsFQu+AO5dFiDceJA+IW11sCN5e/9gKWuHxzxwKepvA8S0lyPwQWYqk1V/O/bV5s5uD5zrI6Wndd8Yq7ejYsWd6ZeHbjrcTbbr35lKsTcpoLxtBjD+H/1X5+pYAPlW7ymLWpPxEAug8tnpzy5HVMtwNZ3br8S5afC9BgR7abGcfa3PjyYvM1tk6+JfCPLa9gIJShu1+eXTq0GUQnxu/0bL/ihnwadHlFZyLVzmRjBkPrsuQsM6KTvXS5Gw+BGo+zy/p7iFUFHws1r6zZNONe0ZxTudpDgGN7IoXmvyj7OOJ7VyGROggB0Ptu2iiNz81uTT1M9z+QCR2pa9E+a6fTWTZIXP86acb8HuKDb8Rd9wkU4G609aZc3QHX0ANKIFSWqhz9WDh0XZgT8daMuda+TmtI4NkTS7j9yQLqvbYQ1W2RpCKVBLgXCgiylBoHfRWFZ/dlOdkojBWaFnWa8ytU69IHFgVAd4V4RVPgjNyWgwovtQtiAvpT3o='
if (verifyDigitalSignature(signature, publicKey, requestBody)) {
console.log('Signature is valid!')
} else {
console.log('Signature is not valid.')
}
Full featured example of handling webhook built with AWS lambda.
import crypto from 'crypto'
import fetch from 'node-fetch'
const lambda = async event => {
const { headers, body } = event
// Signature is a base64 encoded string in header 'siganture'
const signature = headers['signature']
console.log('webhook received')
if (!signature) {
console.log('Webhook does not contain signature. Unauthorized')
return { statusCode: 401 }
}
// Public Key returned as base64 encoded string
const publicKey = await getPublicKey()
console.log('attempting to verify RSA-SHA256 signature')
const verification = verifyDigitalSignature(body, publicKey, signature)
if (!verification) {
console.log('Unable to verify signature. Unauthorized')
return { statusCode: 401 }
}
console.log('signature verification passed')
return { statusCode: 200 }
}
/**
* Returns true if signature verification passed
*
* @remarks
* The crypto package is a built-in Node module
*
* @param body - Stringified body of the webhook
* @param publicKey - Base64 encoded publicKey. Public key can be fetched at GET /service-orders/public-key
* @param signature - Base64 encoded signature. Found in webhook ['signature'] header.
* @returns true if the verification passed
*/
function verifyDigitalSignature(
body: string,
publicKey: string,
signature: string,
): boolean {
try {
return crypto.verify(
'RSA-SHA256',
Buffer.from(body),
Buffer.from(publicKey, 'base64'),
Buffer.from(signature, 'base64'),
)
} catch (e) {
console.log(e)
return false
}
}
async function getPublicKey(): Promise<string> {
console.log('Getting Extend Public Key')
const API_HOST = 'https://api.helloextend.com/'
const url = `${API_HOST}/service-orders/public-key`
const publicKeyResponse = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
const key = (await publicKeyResponse.json()).key
if (!key) {
throw new Error('Unable to parse public key')
}
return key
}