Webhooks
Subscribe your systems to workspace events. Every payload is signed; verifyWebhookSignature() rejects tampered or replayed deliveries.


On this page
Quick example
Type-checked against @medalsocial/sdk v1.3.0 (2026-07-20). A release that changes a signature fails this page's build.
import { Medal, verifyWebhookSignature } from '@medalsocial/sdk';
const medal = new Medal(process.env.MEDAL_API_KEY as string);
// Register an endpoint (idempotency key recommended for writes)
const { data: endpoint } = await medal.webhooks.create(
{
name: 'CRM sync',
url: 'https://example.com/hooks/medal',
event_types: ['contact.created', 'post.published'],
},
{ idempotencyKey: 'crm-sync-setup-1' }
);
// Send a test delivery
await medal.webhooks.test(endpoint.id);
// In your receiver: verify the signature before trusting the payload
export async function handler(rawBody: string, signature: string, timestamp: string) {
const event = await verifyWebhookSignature({
payload: rawBody,
signature,
timestamp,
secret: process.env.MEDAL_WEBHOOK_SECRET as string,
});
console.log(event.type);
}medal.webhooks.list()
List all webhook endpoints in the workspace.
list(): Promise<ApiResponse<WebhookEndpoint[]>>medal.webhooks.create()
Create a webhook endpoint. Returns HTTP 201.
**The response's `data.secret` contains the signing secret EXACTLY ONCE.**
It can never be retrieved again — store it securely immediately. You need
it to verify the `X-Medal-Signature` header on incoming deliveries (see
`verifyWebhookSignature`).
`secret` is typed optional because an idempotent replay (retrying with the
same `Idempotency-Key`, `X-Idempotent-Replayed: true`) returns the existing
endpoint WITHOUT the secret — handle that case (rotate if you lost it).
create(input: CreateWebhookInput, options?: RequestOptions): Promise<ApiResponse<WebhookEndpoint>>medal.webhooks.get()
Get a webhook endpoint by ID.
get(id: string): Promise<ApiResponse<WebhookEndpoint>>medal.webhooks.update()
Update a webhook endpoint (name, url, event types, filters, enabled).
update(id: string, input: UpdateWebhookInput, options?: RequestOptions): Promise<ApiResponse<WebhookEndpoint>>medal.webhooks.delete()
Permanently delete a webhook endpoint (stops all outbound deliveries).
Capability-scoped tokens must pass `idempotencyKey` — the API requires
`Idempotency-Key` + `X-Capability-Confirmation` for direct capability
grants on this route. API keys with legacy scopes may omit it.
delete(id: string, options?: RequestOptions): Promise<ApiResponse<WebhookDeleteResult>>medal.webhooks.deliveries()
List recent deliveries for an endpoint (most recent first).
deliveries(id: string, options?: ListDeliveriesOptions): Promise<ApiResponse<WebhookDelivery[]>>medal.webhooks.test()
Queue a signed `test.ping` delivery to the endpoint. Returns HTTP 202.
test(id: string): Promise<ApiResponse<WebhookTestResult>>verifyWebhookSignature()
Verifies a delivery came from Medal and is fresh: checks the HMAC signature against your endpoint secret and rejects stale timestamps.
declare function verifyWebhookSignature(input: VerifyWebhookSignatureInput): Promise<WebhookEvent>