Twitter Developers Documentation says like below,
Register your webhook URL. You will make a POST request to a /webhooks.json?url= endpoint. When you make this request Twitter will issue a CRC request to your web app. When a webhook is successfully registered, the response will include a webhook id. This webhook id is needed later when making some requests to the Account Activity API.
— 3. Develop webhook consumer app from https://developer.twitter.com/en/docs/accounts-and-users/subscribe-account-activity/guides/getting-started-with-webhooks#webhook-url
but I can’t get CRC request to my web app yet.
And I’ve read and met these requirements but Twitter API says “Webhook URL does not meet the requirements.” with error code 214.
Response requirements:
A base64 encoded HMAC SHA-256 hash created from the crc_token and your app Consumer Secret
Valid response_token and JSON format.
Latency less than 3 seconds.
200 HTTP response code.
— Challenge-Response Checks from https://developer.twitter.com/en/docs/accounts-and-users/subscribe-account-activity/guides/securing-webhooks
Of course, I saw the error troubleshooting guide.
Code 214 - Webhook URL does not meet the requirements.
Please make sure that you are using https.
Your webhook URL could be malformed.
Learn more about how to set up your webhook URL under the Develop webhook consumer app section on Getting started with webhooks page.
— Error troubleshooting guide from https://developer.twitter.com/en/docs/accounts-and-users/subscribe-account-activity/FAQ#troubleshooting
I’m using HTTPS (TLS 1.1 and above), checked the URL malformed, and saw the Develop webhook consumer app section on Getting started with webhooks page. So I can’t understand why were my register requests always rejected.
Anyway, I’m forced to stop trying to get Webhooks over 3 days.
Please help me.
Overview
Registering Premium Account Activity API Webhook subscription was failed.
Architecture:
[ Twitter Server | Users ]
↑
CDN & DNS
↓
[ Cloudflare ]
↑
DDoS Protection
↓
[ nginx ]
↑
Proxy
↓
[ Kestrel ] (ASP.NET Core Internal Server)
Request and Response
Using twurl 0.9.3
$ twurl -t -X POST '/1.1/account_activity/all/prod/webhooks.json?url=https%3A%2F%2Fexample.com%2Fapi%2Fwebhooks%2Ftwitter'
opening connection to api.twitter.com:443...
opened
starting SSL for api.twitter.com:443...
SSL established
<- "POST /1.1/account_activity/all/prod/webhooks.json?url=https%253A%252F%252Fexample.com%252Fapi%252Fwebhooks%252Ftwitter HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: OAuth gem v0.5.4\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: OAuth ***\r\nConnection: close\r\nHost: api.twitter.com\r\nContent-Length: 0\r\n\r\n"
<- ""
-> "HTTP/1.1 400 Bad Request\r\n"
-> "cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0\r\n"
-> "connection: close\r\n"
-> "content-disposition: attachment; filename=json.json\r\n"
-> "content-encoding: gzip\r\n"
-> "content-length: 145\r\n"
-> "content-type: application/json; charset=utf-8\r\n"
-> "date: Sun, 29 Jul 2018 07:51:47 GMT\r\n"
-> "expires: Tue, 31 Mar 1981 05:00:00 GMT\r\n"
-> "last-modified: Sun, 29 Jul 2018 07:51:46 GMT\r\n"
-> "pragma: no-cache\r\n"
-> "server: tsa_m\r\n"
-> "set-cookie: personalization_id=\"v1_***secret***\"; Expires=Tue, 28 Jul 2020 07:51:46 GMT; Path=/; Domain=.twitter.com\r\n"
-> "set-cookie: guest_id=v1%3A***secret***; Expires=Tue, 28 Jul 2020 07:51:46 GMT; Path=/; Domain=.twitter.com\r\n"
-> "strict-transport-security: max-age=631138519\r\n"
-> "x-connection-hash: ***secret***\r\n"
-> "x-content-type-options: nosniff\r\n"
-> "x-frame-options: SAMEORIGIN\r\n"
-> "x-response-time: 156\r\n"
-> "x-xss-protection: 1; mode=block; report=https://twitter.com/i/xss_report\r\n"
-> "\r\n"
reading 145 bytes...
-> ""
-> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x00$\xCC1\n\xC2@\x10\x05\xD0\xAB\fSK\x16\xC5j\xCF`!\x82XH\x8A\xB8\xF9$\xC1\xEC\x8E\xCE\xCC\x9A\"\xE4\xEE\n\x96\xAFy+CU\xD48\xDEWN\xD2\x83\xE3a\x7F\xDCq\x86Y7\xFC\xC47<F\x91']/'\xEA\x05FE\x9C2\xE0\xE4#H\xF1\xAE\x93\"\xA3\xB85t\x9E\xD1\x19(I\xB1:{\xA4\xD1\xFDe1\x84\x1E\x9F\xC6\x97\xC9\x1D\xDA$\xC9a\xF9\x9F\x16\f\xA9\xEAT\x06\xDE\xDA\xED\v\x00\x00\xFF\xFF\x03\x00\x17\x80\xFF\x93\x8B\x00\x00\x00"
{"errors":[{"code":214,"message":"Webhook URL does not meet the requirements. Please consult: https://dev.twitter.com/webhooks/securing"}]}read 145 bytes
Conn close
Server Logs
Expected
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://example.com/api/webhooks/twitter?crc_token=test
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "Twitter", controller = "Webhooks"}. Executing action MyNamespace.Controllers.WebhooksController.Twitter (MyNamespace)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method MyNamespace.Controllers.WebhooksController.Twitter (MyNamespace) with arguments (test) - Validation state: Valid
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action method MyNamespace.Controllers.WebhooksController.Twitter (MyNamespace), returned result Microsoft.AspNetCore.Mvc.OkObjectResult in 0.0643ms.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'MyNamespace.Models.CRCResponse'.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action MyNamespace.Controllers.WebhooksController.Twitter (MyNamespace) in 0.6367ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 1.0022ms 200 application/json; charset=utf-8
Actual
THERE ARE NO LOGS
Server
nginx configuration:
upstream myserver {
server localhost:5001;
keepalive 128;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mysecretdomain.net;
access_log logs/mysecretdomain.log;
ssl_certificate ../certs/mysecretdomain.pem;
ssl_certificate_key ../certs/mysecretdomain.key;
ssl_client_certificate ../cloudflare.pem;
ssl_verify_client on;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate ../cloudflare_origin.pem;
ssl_session_tickets on;
ssl_session_cache shared:SSL:100m;
ssl_session_timeout 1d;
ssl_dhparam ../dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security max-age=31536000;
location / {
proxy_pass https://myserver;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api {
allow 199.59.148.0/22;
allow 199.16.156.0/22;
deny all;
proxy_pass https://myserver;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
ASP.NET Core codes:
// GET webhooks/twitter
[HttpGet("[action]")]
public IActionResult Twitter([FromQuery(Name = "crc_token")] string crcToken) =>
crcToken is null ? NoContent() : Ok(new CRCResponse(_configuration["Twitter:ConsumerSecret"], crcToken)) as IActionResult;
public class CRCResponse
{
private static KeyedHashAlgorithm _hmac = KeyedHashAlgorithm.Create("HMACSHA256");
public CRCResponse()
{
}
public CRCResponse(string key, string value)
{
_hmac.Key = Encoding.ASCII.GetBytes(key);
ResponseToken = $"sha256={Convert.ToBase64String(_hmac.ComputeHash(Encoding.ASCII.GetBytes(value)))}";
}
[JsonProperty("response_token")]
public string ResponseToken { get; set; }
}