Authentication
The API uses secure authentication to protect airdrop operations:
- Bearer Token - Required for ALL requests
- Request Signature - HMAC-SHA256 signature (required for airdrop requests only)
Required Headers
All Requests (Health, Stats, etc.)
Authorization: Bearer YOUR_BEARER_TOKENAirdrop Requests
Authorization: Bearer YOUR_BEARER_TOKEN
X-Signature: v1,t=...,s=...Getting Your Credentials
Contact the Movement team to receive:
- Bearer Token - For authenticating all API requests
- Signing Secret - For generating HMAC-SHA256 signatures on airdrop requests
Environment-Specific Credentials: Testnet and mainnet use different bearer tokens and signing secrets. Make sure you use the correct credentials for each environment.
Request Signatures
All airdrop requests must include an HMAC-SHA256 signature.
Signature Format
Include the signature in the X-Signature header:
X-Signature: v1,t=1700000000,s=abc123def456...Where:
v1= signature versiont= Unix timestamp (seconds)s= HMAC-SHA256 signature
Generating Signatures
The signature is computed over this payload:
v1.timestamp.METHOD.PATH.BODYExample payload:
v1.1700000000.POST./api/airdrop.{"address":"0x...","amount":"100","uniqueHash":"abc..."}TypeScript Example
import crypto from 'crypto';
function generateSignature(
signingSecret: string,
method: string,
path: string,
body: object
): string {
const timestamp = Math.floor(Date.now() / 1000);
const bodyString = JSON.stringify(body);
const payload = `v1.${timestamp}.${method}.${path}.${bodyString}`;
const signature = crypto
.createHmac('sha256', signingSecret)
.update(payload)
.digest('hex');
return `v1,t=${timestamp},s=${signature}`;
}
// Usage
const signature = generateSignature(
SIGNING_SECRET,
'POST',
'/api/airdrop',
{
address: '0x1234...',
amount: '100',
uniqueHash: 'abc123...'
}
);Complete Client Example
import crypto from 'crypto';
class AirdropClient {
constructor(
private bearerToken: string,
private signingSecret: string,
private baseUrl: string = 'https://YOUR_API_DOMAIN'
) {}
generateSignature(method: string, path: string, body: object): string {
const timestamp = Math.floor(Date.now() / 1000);
const bodyString = JSON.stringify(body);
const payload = `v1.${timestamp}.${method}.${path}.${bodyString}`;
const signature = crypto
.createHmac('sha256', this.signingSecret)
.update(payload)
.digest('hex');
return `v1,t=${timestamp},s=${signature}`;
}
async processAirdrop(address: string, amount: string, uniqueHash: string) {
const path = '/api/airdrop';
const body = { address, amount, uniqueHash };
const signature = this.generateSignature('POST', path, body);
const response = await fetch(`${this.baseUrl}${path}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.bearerToken}`,
'X-Signature': signature,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Airdrop failed: ${error.error?.message || 'Unknown error'}`);
}
return response.json();
}
async checkHealth() {
const response = await fetch(`${this.baseUrl}/api/health`, {
headers: {
'Authorization': `Bearer ${this.bearerToken}`,
}
});
return response.json();
}
}
// Initialize client
const client = new AirdropClient(
process.env.AIRDROP_BEARER_TOKEN!,
process.env.AIRDROP_SIGNING_SECRET!
);
// Process an airdrop
try {
const result = await client.processAirdrop(
'0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
'100',
crypto.randomBytes(32).toString('hex')
);
console.log('Airdrop successful:', result.transactionHash);
} catch (error) {
console.error('Airdrop failed:', error.message);
}Compromised Credentials? If you suspect your credentials have been compromised, contact the Movement team immediately to revoke and reissue new credentials.
Common Authentication Errors
401 Unauthorized - Missing Authorization Header
{
"error": "Unauthorized",
"message": "Missing or invalid Authorization."
}Solution: Include Authorization: Bearer YOUR_TOKEN header in all requests.
401 Unauthorized - Invalid Bearer Token
{
"error": "Unauthorized",
"message": "Invalid bearer token"
}Solution: Verify your bearer token is correct and hasn’t expired.
401 Unauthorized - Invalid Signature
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid request signature"
}
}Solution: Check your signature generation:
- Verify signing secret is correct
- Ensure payload format matches exactly:
v1.timestamp.METHOD.PATH.BODY - Body must be JSON stringified with no extra whitespace
401 Unauthorized - Expired Signature
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Signature timestamp is too old or in the future"
}
}Solution:
- Ensure your system clock is synchronized (use NTP)
- Don’t reuse signatures (generate fresh for each request)
- Signatures are valid for 5 minutes